glaip-sdk 0.7.27__py3-none-any.whl → 0.7.29__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.
- glaip_sdk/cli/slash/tui/__init__.py +2 -2
- glaip_sdk/cli/slash/tui/accounts_app.py +109 -47
- glaip_sdk/cli/slash/tui/remote_runs_app.py +854 -580
- glaip_sdk/cli/slash/tui/toast.py +29 -3
- glaip_sdk/utils/import_resolver.py +38 -1
- {glaip_sdk-0.7.27.dist-info → glaip_sdk-0.7.29.dist-info}/METADATA +1 -1
- {glaip_sdk-0.7.27.dist-info → glaip_sdk-0.7.29.dist-info}/RECORD +10 -10
- {glaip_sdk-0.7.27.dist-info → glaip_sdk-0.7.29.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.7.27.dist-info → glaip_sdk-0.7.29.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.7.27.dist-info → glaip_sdk-0.7.29.dist-info}/top_level.txt +0 -0
|
@@ -10,8 +10,8 @@ from glaip_sdk.cli.slash.tui.keybind_registry import (
|
|
|
10
10
|
parse_key_sequence,
|
|
11
11
|
)
|
|
12
12
|
from glaip_sdk.cli.slash.tui.remote_runs_app import (
|
|
13
|
-
RemoteRunsTextualApp,
|
|
14
13
|
RemoteRunsTUICallbacks,
|
|
14
|
+
RunsHarlequinScreen,
|
|
15
15
|
run_remote_runs_textual,
|
|
16
16
|
)
|
|
17
17
|
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities, detect_terminal_background
|
|
@@ -23,7 +23,7 @@ __all__ = [
|
|
|
23
23
|
"ToastVariant",
|
|
24
24
|
"TerminalCapabilities",
|
|
25
25
|
"detect_terminal_background",
|
|
26
|
-
"
|
|
26
|
+
"RunsHarlequinScreen",
|
|
27
27
|
"RemoteRunsTUICallbacks",
|
|
28
28
|
"run_remote_runs_textual",
|
|
29
29
|
"KeybindRegistry",
|
|
@@ -52,7 +52,7 @@ from textual.app import App, ComposeResult
|
|
|
52
52
|
from textual.binding import Binding
|
|
53
53
|
from textual.containers import Horizontal, Vertical
|
|
54
54
|
from textual.coordinate import Coordinate
|
|
55
|
-
from textual.screen import ModalScreen
|
|
55
|
+
from textual.screen import ModalScreen, Screen
|
|
56
56
|
from textual.suggester import SuggestFromList
|
|
57
57
|
from textual.theme import Theme
|
|
58
58
|
from textual.widgets import Button, Checkbox, DataTable, Footer, Input, Static
|
|
@@ -107,6 +107,37 @@ class AccountsTUICallbacks:
|
|
|
107
107
|
switch_account: Callable[[str], tuple[bool, str]]
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def _ensure_context_services(
|
|
111
|
+
ctx: TUIContext,
|
|
112
|
+
notify: Callable[[ToastBus.Changed], None],
|
|
113
|
+
) -> tuple[KeybindRegistry | None, ToastBus | None, ClipboardAdapter]:
|
|
114
|
+
"""Ensure the context has keybinds, toasts, and clipboard services."""
|
|
115
|
+
if ctx.keybinds is None:
|
|
116
|
+
ctx.keybinds = KeybindRegistry()
|
|
117
|
+
if ctx.toasts is None:
|
|
118
|
+
ctx.toasts = ToastBus(on_change=notify)
|
|
119
|
+
if ctx.clipboard is None:
|
|
120
|
+
ctx.clipboard = ClipboardAdapter(terminal=ctx.terminal)
|
|
121
|
+
return ctx.keybinds, ctx.toasts, ctx.clipboard
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _initialize_fallback_services(
|
|
125
|
+
notify: Callable[[ToastBus.Changed], None],
|
|
126
|
+
) -> tuple[KeybindRegistry | None, ToastBus | None, ClipboardAdapter]:
|
|
127
|
+
"""Create fallback clipboard and toast services without a context."""
|
|
128
|
+
terminal = TerminalCapabilities(tty=True, ansi=True, osc52=False, osc11_bg=None, mouse=False, truecolor=False)
|
|
129
|
+
clipboard = ClipboardAdapter(terminal=terminal)
|
|
130
|
+
toast_bus = ToastBus(on_change=notify)
|
|
131
|
+
return None, toast_bus, clipboard
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _wire_toast_notifications(toast_bus: ToastBus | None, notify: Callable[[ToastBus.Changed], None]) -> None:
|
|
135
|
+
"""Ensure toast bus forwards notifications to the provided callback."""
|
|
136
|
+
if toast_bus is None:
|
|
137
|
+
return
|
|
138
|
+
toast_bus.subscribe(notify)
|
|
139
|
+
|
|
140
|
+
|
|
110
141
|
def _build_account_rows_from_store(
|
|
111
142
|
store: AccountStore,
|
|
112
143
|
env_lock: bool,
|
|
@@ -232,7 +263,12 @@ def run_accounts_textual(
|
|
|
232
263
|
if not TEXTUAL_SUPPORTED:
|
|
233
264
|
return
|
|
234
265
|
app = AccountsTextualApp(rows, active_account, env_lock, callbacks, ctx=ctx)
|
|
235
|
-
|
|
266
|
+
mouse_enabled = True
|
|
267
|
+
if ctx is not None:
|
|
268
|
+
terminal = getattr(ctx, "terminal", None)
|
|
269
|
+
if terminal is not None:
|
|
270
|
+
mouse_enabled = bool(getattr(terminal, "mouse", True))
|
|
271
|
+
app.run(mouse=mouse_enabled)
|
|
236
272
|
|
|
237
273
|
|
|
238
274
|
class AccountFormModal(_AccountFormBase): # pragma: no cover - interactive
|
|
@@ -543,8 +579,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
543
579
|
)
|
|
544
580
|
|
|
545
581
|
# Toast container for notifications
|
|
546
|
-
|
|
547
|
-
yield ToastContainer(Toast(), id="toast-container")
|
|
582
|
+
yield ToastContainer(Toast(), id="toast-container")
|
|
548
583
|
|
|
549
584
|
def on_mount(self) -> None:
|
|
550
585
|
"""Configure the screen after mount."""
|
|
@@ -567,29 +602,31 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
567
602
|
"""Initialize TUI context services."""
|
|
568
603
|
|
|
569
604
|
def _notify(message: ToastBus.Changed) -> None:
|
|
605
|
+
# Post to self (Screen) so ToastHandlerMixin.on_toast_bus_changed can handle it.
|
|
570
606
|
self.post_message(message)
|
|
571
607
|
|
|
572
608
|
ctx = self.ctx if hasattr(self, "ctx") else self._ctx
|
|
573
|
-
if ctx:
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
)
|
|
587
|
-
self.
|
|
588
|
-
|
|
589
|
-
|
|
609
|
+
if ctx is None:
|
|
610
|
+
self._keybinds, self._toast_bus, self._clip_cache = _initialize_fallback_services(_notify)
|
|
611
|
+
return
|
|
612
|
+
|
|
613
|
+
self._keybinds, self._toast_bus, self._clip_cache = _ensure_context_services(ctx, _notify)
|
|
614
|
+
_wire_toast_notifications(self._toast_bus, _notify)
|
|
615
|
+
|
|
616
|
+
def _ensure_toast_bus(self) -> None:
|
|
617
|
+
"""Ensure toast bus is initialized with proper message routing."""
|
|
618
|
+
if self._toast_bus is not None:
|
|
619
|
+
return
|
|
620
|
+
|
|
621
|
+
def _notify(message: ToastBus.Changed) -> None:
|
|
622
|
+
# Post to self (Screen) so ToastHandlerMixin.on_toast_bus_changed can handle it.
|
|
623
|
+
self.post_message(message)
|
|
624
|
+
|
|
625
|
+
self._toast_bus = ToastBus(on_change=_notify)
|
|
590
626
|
|
|
591
627
|
def _prepare_toasts(self) -> None:
|
|
592
628
|
"""Prepare toast system."""
|
|
629
|
+
self._ensure_toast_bus()
|
|
593
630
|
if self._toast_bus:
|
|
594
631
|
self._toast_bus.clear()
|
|
595
632
|
|
|
@@ -1230,26 +1267,33 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1230
1267
|
|
|
1231
1268
|
def _initialize_context_services(self) -> None:
|
|
1232
1269
|
def _notify(message: ToastBus.Changed) -> None:
|
|
1233
|
-
|
|
1270
|
+
"""Route messages to the active screen or app."""
|
|
1271
|
+
screen = self._safe_screen()
|
|
1272
|
+
if screen is not None:
|
|
1273
|
+
self._safe_post_message(screen, message)
|
|
1274
|
+
return
|
|
1275
|
+
self._safe_post_message(self, message)
|
|
1234
1276
|
|
|
1235
|
-
if self._ctx:
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1277
|
+
if self._ctx is None:
|
|
1278
|
+
self._keybinds, self._toast_bus, self._clip_cache = _initialize_fallback_services(_notify)
|
|
1279
|
+
return
|
|
1280
|
+
|
|
1281
|
+
self._keybinds, self._toast_bus, self._clip_cache = _ensure_context_services(self._ctx, _notify)
|
|
1282
|
+
_wire_toast_notifications(self._toast_bus, _notify)
|
|
1283
|
+
|
|
1284
|
+
def _safe_screen(self) -> Screen | None:
|
|
1285
|
+
"""Return the active screen when available."""
|
|
1286
|
+
try:
|
|
1287
|
+
return self.screen
|
|
1288
|
+
except Exception:
|
|
1289
|
+
return None
|
|
1290
|
+
|
|
1291
|
+
def _safe_post_message(self, target: Screen | App[Any], message: ToastBus.Changed) -> None:
|
|
1292
|
+
"""Post a toast message while guarding against failures."""
|
|
1293
|
+
try:
|
|
1294
|
+
target.post_message(message)
|
|
1295
|
+
except Exception:
|
|
1296
|
+
return
|
|
1253
1297
|
|
|
1254
1298
|
def _prepare_toasts(self) -> None:
|
|
1255
1299
|
"""Prepare toast system by clearing any existing toasts."""
|
|
@@ -1517,7 +1561,11 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1517
1561
|
def _clear_filter(self) -> None:
|
|
1518
1562
|
"""Clear the filter input and reset filter state."""
|
|
1519
1563
|
# Skip if Harlequin screen is active (it handles its own filtering)
|
|
1520
|
-
|
|
1564
|
+
try:
|
|
1565
|
+
screen = self.screen
|
|
1566
|
+
except Exception:
|
|
1567
|
+
screen = None
|
|
1568
|
+
if isinstance(screen, AccountsHarlequinScreen):
|
|
1521
1569
|
return
|
|
1522
1570
|
try:
|
|
1523
1571
|
filter_input = self.query_one(FILTER_INPUT_ID, Input)
|
|
@@ -1567,8 +1615,12 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1567
1615
|
UX note: helps users reset the list without leaving the TUI.
|
|
1568
1616
|
"""
|
|
1569
1617
|
# Skip if Harlequin screen is active (it handles its own exit)
|
|
1570
|
-
|
|
1571
|
-
self.
|
|
1618
|
+
try:
|
|
1619
|
+
screen = self.screen
|
|
1620
|
+
except Exception:
|
|
1621
|
+
screen = None
|
|
1622
|
+
if isinstance(screen, AccountsHarlequinScreen):
|
|
1623
|
+
self._safe_exit()
|
|
1572
1624
|
return
|
|
1573
1625
|
try:
|
|
1574
1626
|
filter_input = self.query_one(FILTER_INPUT_ID, Input)
|
|
@@ -1578,7 +1630,7 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1578
1630
|
self._clear_filter()
|
|
1579
1631
|
self._reload_rows()
|
|
1580
1632
|
try:
|
|
1581
|
-
table = self.query_one(ACCOUNTS_TABLE_ID
|
|
1633
|
+
table = self.query_one(ACCOUNTS_TABLE_ID)
|
|
1582
1634
|
table.focus()
|
|
1583
1635
|
except Exception:
|
|
1584
1636
|
pass
|
|
@@ -1586,11 +1638,17 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1586
1638
|
except Exception:
|
|
1587
1639
|
# Filter input doesn't exist, just exit
|
|
1588
1640
|
pass
|
|
1589
|
-
self.
|
|
1641
|
+
self._safe_exit()
|
|
1590
1642
|
|
|
1591
1643
|
def action_app_exit(self) -> None:
|
|
1592
1644
|
"""Exit the application regardless of focus state."""
|
|
1593
|
-
self.
|
|
1645
|
+
self._safe_exit()
|
|
1646
|
+
|
|
1647
|
+
def _safe_exit(self) -> None:
|
|
1648
|
+
try:
|
|
1649
|
+
self.exit()
|
|
1650
|
+
except AttributeError:
|
|
1651
|
+
return
|
|
1594
1652
|
|
|
1595
1653
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
1596
1654
|
"""Handle filter bar buttons."""
|
|
@@ -1836,7 +1894,11 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1836
1894
|
def _update_filter_button_visibility(self) -> None:
|
|
1837
1895
|
"""Show clear button only when filter has content."""
|
|
1838
1896
|
# Skip if Harlequin screen is active (it doesn't have this button)
|
|
1839
|
-
|
|
1897
|
+
try:
|
|
1898
|
+
screen = self.screen
|
|
1899
|
+
except Exception:
|
|
1900
|
+
screen = None
|
|
1901
|
+
if isinstance(screen, AccountsHarlequinScreen):
|
|
1840
1902
|
return
|
|
1841
1903
|
try:
|
|
1842
1904
|
filter_input = self.query_one(FILTER_INPUT_ID, Input)
|