glaip-sdk 0.7.15__py3-none-any.whl → 0.7.16__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/session.py +7 -3
- glaip_sdk/cli/slash/tui/__init__.py +3 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +6 -1
- glaip_sdk/cli/slash/tui/accounts_app.py +67 -66
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/loading.py +43 -21
- glaip_sdk/cli/slash/tui/remote_runs_app.py +19 -31
- glaip_sdk/runner/deps.py +4 -1
- {glaip_sdk-0.7.15.dist-info → glaip_sdk-0.7.16.dist-info}/METADATA +1 -1
- {glaip_sdk-0.7.15.dist-info → glaip_sdk-0.7.16.dist-info}/RECORD +13 -12
- {glaip_sdk-0.7.15.dist-info → glaip_sdk-0.7.16.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.7.15.dist-info → glaip_sdk-0.7.16.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.7.15.dist-info → glaip_sdk-0.7.16.dist-info}/top_level.txt +0 -0
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -333,7 +333,8 @@ class SlashSession:
|
|
|
333
333
|
current_status[0] = "Checking for updates..."
|
|
334
334
|
if status_callback:
|
|
335
335
|
status_callback(current_status[0])
|
|
336
|
-
|
|
336
|
+
# Defer update prompt if we are in animated initialization to avoid blocking/cluttering
|
|
337
|
+
self._maybe_show_update_prompt(defer=bool(status_callback is None))
|
|
337
338
|
return True
|
|
338
339
|
|
|
339
340
|
def _update_pulse_step(
|
|
@@ -1619,9 +1620,12 @@ class SlashSession:
|
|
|
1619
1620
|
self.console.print()
|
|
1620
1621
|
self.console.print(banner)
|
|
1621
1622
|
|
|
1622
|
-
def _maybe_show_update_prompt(self) -> None:
|
|
1623
|
+
def _maybe_show_update_prompt(self, *, defer: bool = False) -> None:
|
|
1623
1624
|
"""Display update prompt once per session when applicable."""
|
|
1624
|
-
if self._update_prompt_shown:
|
|
1625
|
+
if self._update_prompt_shown or (defer and not self._update_prompt_shown):
|
|
1626
|
+
if defer:
|
|
1627
|
+
# Just mark as ready to show, but don't show yet
|
|
1628
|
+
return
|
|
1625
1629
|
return
|
|
1626
1630
|
|
|
1627
1631
|
self._update_notifier(
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter, ClipboardResult
|
|
4
4
|
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
5
|
+
from glaip_sdk.cli.slash.tui.indicators import PulseIndicator
|
|
5
6
|
from glaip_sdk.cli.slash.tui.keybind_registry import (
|
|
6
7
|
Keybind,
|
|
7
8
|
KeybindRegistry,
|
|
8
9
|
format_key_sequence,
|
|
9
10
|
parse_key_sequence,
|
|
10
11
|
)
|
|
11
|
-
from glaip_sdk.cli.slash.tui.toast import ToastBus, ToastVariant
|
|
12
12
|
from glaip_sdk.cli.slash.tui.remote_runs_app import (
|
|
13
13
|
RemoteRunsTextualApp,
|
|
14
14
|
RemoteRunsTUICallbacks,
|
|
15
15
|
run_remote_runs_textual,
|
|
16
16
|
)
|
|
17
17
|
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities, detect_terminal_background
|
|
18
|
+
from glaip_sdk.cli.slash.tui.toast import ToastBus, ToastVariant
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
20
21
|
"TUIContext",
|
|
@@ -31,4 +32,5 @@ __all__ = [
|
|
|
31
32
|
"format_key_sequence",
|
|
32
33
|
"ClipboardAdapter",
|
|
33
34
|
"ClipboardResult",
|
|
35
|
+
"PulseIndicator",
|
|
34
36
|
]
|
|
@@ -79,7 +79,6 @@ Button:hover {
|
|
|
79
79
|
|
|
80
80
|
#accounts-loading {
|
|
81
81
|
width: 8;
|
|
82
|
-
display: none;
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
#status {
|
|
@@ -165,6 +164,12 @@ Button:hover {
|
|
|
165
164
|
height: auto;
|
|
166
165
|
}
|
|
167
166
|
|
|
167
|
+
#harlequin-loading {
|
|
168
|
+
width: auto;
|
|
169
|
+
height: 3;
|
|
170
|
+
padding: 0 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
168
173
|
#harlequin-status {
|
|
169
174
|
padding: 1;
|
|
170
175
|
margin-top: 1;
|
|
@@ -30,77 +30,40 @@ from glaip_sdk.cli.slash.accounts_shared import (
|
|
|
30
30
|
from glaip_sdk.cli.slash.tui.background_tasks import BackgroundTaskMixin
|
|
31
31
|
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter, ClipboardResult
|
|
32
32
|
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
33
|
+
from glaip_sdk.cli.slash.tui.indicators import PulseIndicator
|
|
33
34
|
from glaip_sdk.cli.slash.tui.keybind_registry import KeybindRegistry
|
|
34
35
|
from glaip_sdk.cli.slash.tui.layouts.harlequin import HarlequinScreen
|
|
35
36
|
from glaip_sdk.cli.slash.tui.loading import hide_loading_indicator, show_loading_indicator
|
|
36
37
|
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities
|
|
37
38
|
from glaip_sdk.cli.slash.tui.theme.catalog import _BUILTIN_THEMES
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
except Exception: # pragma: no cover - optional dependency
|
|
49
|
-
ClipboardToastMixin = object # type: ignore[assignment, misc]
|
|
50
|
-
Toast = None # type: ignore[assignment]
|
|
51
|
-
ToastBus = None # type: ignore[assignment]
|
|
52
|
-
ToastContainer = None # type: ignore[assignment]
|
|
53
|
-
ToastHandlerMixin = object # type: ignore[assignment, misc]
|
|
54
|
-
ToastVariant = None # type: ignore[assignment]
|
|
40
|
+
from glaip_sdk.cli.slash.tui.toast import (
|
|
41
|
+
ClipboardToastMixin,
|
|
42
|
+
Toast,
|
|
43
|
+
ToastBus,
|
|
44
|
+
ToastContainer,
|
|
45
|
+
ToastHandlerMixin,
|
|
46
|
+
ToastVariant,
|
|
47
|
+
)
|
|
55
48
|
from glaip_sdk.cli.validators import validate_api_key
|
|
56
49
|
from glaip_sdk.utils.validation import validate_url
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Coordinate = None # type: ignore[assignment]
|
|
75
|
-
Button = None # type: ignore[assignment]
|
|
76
|
-
Checkbox = None # type: ignore[assignment]
|
|
77
|
-
DataTable = None # type: ignore[assignment]
|
|
78
|
-
Footer = None # type: ignore[assignment]
|
|
79
|
-
Header = None # type: ignore[assignment]
|
|
80
|
-
Input = None # type: ignore[assignment]
|
|
81
|
-
LoadingIndicator = None # type: ignore[assignment]
|
|
82
|
-
ModalScreen = None # type: ignore[assignment]
|
|
83
|
-
Static = None # type: ignore[assignment]
|
|
84
|
-
SuggestFromList = None # type: ignore[assignment]
|
|
85
|
-
Theme = None # type: ignore[assignment]
|
|
86
|
-
|
|
87
|
-
if App is not None:
|
|
88
|
-
try: # pragma: no cover - optional dependency
|
|
89
|
-
from textual.theme import Theme
|
|
90
|
-
except Exception: # pragma: no cover - optional dependency
|
|
91
|
-
Theme = None # type: ignore[assignment]
|
|
92
|
-
|
|
93
|
-
TEXTUAL_SUPPORTED = App is not None and DataTable is not None
|
|
94
|
-
|
|
95
|
-
# Use safe bases so the module remains importable without Textual installed.
|
|
96
|
-
if TEXTUAL_SUPPORTED:
|
|
97
|
-
_AccountFormBase = ModalScreen[dict[str, Any] | None]
|
|
98
|
-
_ConfirmDeleteBase = ModalScreen[str | None]
|
|
99
|
-
_AppBase = App[None]
|
|
100
|
-
else:
|
|
101
|
-
_AccountFormBase = object
|
|
102
|
-
_ConfirmDeleteBase = object
|
|
103
|
-
_AppBase = object
|
|
51
|
+
from textual.app import App, ComposeResult
|
|
52
|
+
from textual.binding import Binding
|
|
53
|
+
from textual.containers import Horizontal, Vertical
|
|
54
|
+
from textual.coordinate import Coordinate
|
|
55
|
+
from textual.screen import ModalScreen
|
|
56
|
+
from textual.suggester import SuggestFromList
|
|
57
|
+
from textual.theme import Theme
|
|
58
|
+
from textual.widgets import Button, Checkbox, DataTable, Footer, Input, Static
|
|
59
|
+
|
|
60
|
+
# Harlequin layout requires specific widget support
|
|
61
|
+
TEXTUAL_SUPPORTED = True
|
|
62
|
+
|
|
63
|
+
# Use standard Textual base classes
|
|
64
|
+
_AccountFormBase = ModalScreen[dict[str, Any] | None]
|
|
65
|
+
_ConfirmDeleteBase = ModalScreen[str | None]
|
|
66
|
+
_AppBase = App[None]
|
|
104
67
|
|
|
105
68
|
# Widget IDs for Textual UI
|
|
106
69
|
ACCOUNTS_TABLE_ID = "#accounts-table"
|
|
@@ -390,6 +353,10 @@ class AccountFormModal(_AccountFormBase): # pragma: no cover - interactive
|
|
|
390
353
|
if btn_id == "form-save":
|
|
391
354
|
self._handle_submit()
|
|
392
355
|
|
|
356
|
+
def on_input_submitted(self, _event: Input.Submitted) -> None:
|
|
357
|
+
"""Handle Enter key to save."""
|
|
358
|
+
self._handle_submit()
|
|
359
|
+
|
|
393
360
|
def _handle_submit(self) -> None:
|
|
394
361
|
"""Validate inputs and dismiss with payload on success."""
|
|
395
362
|
status = self.query_one("#form-status", Static)
|
|
@@ -457,6 +424,10 @@ class ConfirmDeleteModal(_ConfirmDeleteBase): # pragma: no cover - interactive
|
|
|
457
424
|
if btn_id == "confirm-delete":
|
|
458
425
|
self._handle_confirm()
|
|
459
426
|
|
|
427
|
+
def on_input_submitted(self, _event: Input.Submitted) -> None:
|
|
428
|
+
"""Handle Enter key in confirmation input."""
|
|
429
|
+
self._handle_confirm()
|
|
430
|
+
|
|
460
431
|
def _handle_confirm(self) -> None:
|
|
461
432
|
"""Dismiss with name when confirmation matches."""
|
|
462
433
|
status = self.query_one("#confirm-status", Static)
|
|
@@ -520,6 +491,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
520
491
|
ctx: Shared TUI context.
|
|
521
492
|
"""
|
|
522
493
|
super().__init__(ctx=ctx)
|
|
494
|
+
self._ctx = ctx
|
|
523
495
|
self._store = get_account_store()
|
|
524
496
|
self._all_rows = rows
|
|
525
497
|
self._active_account = active_account
|
|
@@ -562,6 +534,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
562
534
|
yield Button("(e) Edit", id="action-edit")
|
|
563
535
|
yield Button("(d) Delete", id="action-delete")
|
|
564
536
|
yield Button("(c) Copy", id="action-copy")
|
|
537
|
+
yield PulseIndicator(id="harlequin-loading")
|
|
565
538
|
yield Static("", id="harlequin-status")
|
|
566
539
|
# Help text showing keyboard shortcuts at the bottom
|
|
567
540
|
yield Static(
|
|
@@ -588,6 +561,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
588
561
|
self._prepare_toasts()
|
|
589
562
|
self._register_keybinds()
|
|
590
563
|
self._update_detail_pane()
|
|
564
|
+
self._hide_loading()
|
|
591
565
|
|
|
592
566
|
def _initialize_context_services(self) -> None:
|
|
593
567
|
"""Initialize TUI context services."""
|
|
@@ -753,6 +727,12 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
753
727
|
return None
|
|
754
728
|
return str(self._selected_account.get("name", ""))
|
|
755
729
|
|
|
730
|
+
def _show_loading(self, message: str | None = None) -> None:
|
|
731
|
+
show_loading_indicator(self, "#harlequin-loading", message=message, set_status=self._set_status)
|
|
732
|
+
|
|
733
|
+
def _hide_loading(self) -> None:
|
|
734
|
+
hide_loading_indicator(self, "#harlequin-loading")
|
|
735
|
+
|
|
756
736
|
def action_switch_account(self) -> None:
|
|
757
737
|
"""Switch to the currently selected account."""
|
|
758
738
|
if self._env_lock:
|
|
@@ -806,6 +786,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
806
786
|
self._set_status(f"Switch failed: {exc}", "red")
|
|
807
787
|
return
|
|
808
788
|
finally:
|
|
789
|
+
self._hide_loading()
|
|
809
790
|
self._is_switching = False
|
|
810
791
|
|
|
811
792
|
if switched:
|
|
@@ -822,8 +803,10 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
822
803
|
self._set_status(message or "Switch failed; kept previous account.", "yellow")
|
|
823
804
|
|
|
824
805
|
try:
|
|
806
|
+
self._show_loading(f"Connecting to '{name}'...")
|
|
825
807
|
self.track_task(perform(), logger=logging.getLogger(__name__))
|
|
826
808
|
except Exception as exc:
|
|
809
|
+
self._hide_loading()
|
|
827
810
|
self._is_switching = False
|
|
828
811
|
self._set_status(f"Switch failed to start: {exc}", "red")
|
|
829
812
|
|
|
@@ -881,6 +864,15 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
881
864
|
self._filter_text = (event.value or "").strip()
|
|
882
865
|
self._reload_accounts_list()
|
|
883
866
|
|
|
867
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
868
|
+
"""Handle Enter key in Harlequin filter input."""
|
|
869
|
+
if event.input.id == "harlequin-filter":
|
|
870
|
+
try:
|
|
871
|
+
table = self.query_one(HARLEQUIN_ACCOUNTS_LIST_ID, DataTable)
|
|
872
|
+
table.focus()
|
|
873
|
+
except Exception:
|
|
874
|
+
pass
|
|
875
|
+
|
|
884
876
|
def action_add_account(self) -> None:
|
|
885
877
|
"""Open add account modal."""
|
|
886
878
|
if self._check_env_lock():
|
|
@@ -1144,6 +1136,8 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
1144
1136
|
warning=tokens.warning,
|
|
1145
1137
|
error=tokens.error,
|
|
1146
1138
|
success=tokens.success,
|
|
1139
|
+
background=tokens.background,
|
|
1140
|
+
surface=tokens.background_panel,
|
|
1147
1141
|
)
|
|
1148
1142
|
)
|
|
1149
1143
|
|
|
@@ -1371,16 +1365,23 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1371
1365
|
|
|
1372
1366
|
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
1373
1367
|
"""Apply filter when user presses Enter inside filter input."""
|
|
1368
|
+
# Skip if a screen other than the default app screen is active (e.g., Harlequin or Modal)
|
|
1369
|
+
if self.screen.id != "_default":
|
|
1370
|
+
return
|
|
1371
|
+
|
|
1374
1372
|
self._filter_text = (event.value or "").strip()
|
|
1375
1373
|
self._reload_rows()
|
|
1376
|
-
|
|
1377
|
-
|
|
1374
|
+
try:
|
|
1375
|
+
table = self.query_one(ACCOUNTS_TABLE_ID, DataTable)
|
|
1376
|
+
table.focus()
|
|
1377
|
+
except Exception:
|
|
1378
|
+
pass
|
|
1378
1379
|
self._update_filter_button_visibility()
|
|
1379
1380
|
|
|
1380
1381
|
def on_input_changed(self, event: Input.Changed) -> None:
|
|
1381
1382
|
"""Apply filter live as the user types."""
|
|
1382
|
-
# Skip if
|
|
1383
|
-
if
|
|
1383
|
+
# Skip if a screen other than the default app screen is active (e.g., Harlequin or Modal)
|
|
1384
|
+
if self.screen.id != "_default":
|
|
1384
1385
|
return
|
|
1385
1386
|
self._filter_text = (event.value or "").strip()
|
|
1386
1387
|
self._reload_rows()
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""TUI animated indicators for waiting states."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
from textual._context import NoActiveAppError
|
|
9
|
+
from textual.timer import Timer
|
|
10
|
+
from textual.widgets import Static
|
|
11
|
+
|
|
12
|
+
from glaip_sdk.cli.slash.tui.theme.catalog import _BUILTIN_THEMES
|
|
13
|
+
|
|
14
|
+
DEFAULT_MESSAGE = "Processing…"
|
|
15
|
+
DEFAULT_WIDTH = 20
|
|
16
|
+
DEFAULT_SPEED_MS = 40
|
|
17
|
+
|
|
18
|
+
BAR_GLYPH = " "
|
|
19
|
+
PULSE_GLYPH = "█"
|
|
20
|
+
|
|
21
|
+
VARIANT_STYLES: dict[str, str] = {
|
|
22
|
+
# Default hex colors matching gl-dark theme (see theme/catalog.py)
|
|
23
|
+
# These are used as fallbacks when the app theme is not active
|
|
24
|
+
"accent": "#C77DFF",
|
|
25
|
+
"primary": "#6EA8FE",
|
|
26
|
+
"success": "#34D399",
|
|
27
|
+
"warning": "#FBBF24",
|
|
28
|
+
"error": "#F87171",
|
|
29
|
+
"info": "#60A5FA",
|
|
30
|
+
"subtle": "#9CA3AF",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PulseIndicator(Static):
|
|
35
|
+
"""A Codex-style moving light/pulse indicator for waiting states.
|
|
36
|
+
|
|
37
|
+
Mirrors the 'Knight Rider' / Cylon scanner animation pattern.
|
|
38
|
+
Specified in specs/architecture/cli-textual-animated-indicators/spec.md
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
DEFAULT_CSS = """
|
|
42
|
+
PulseIndicator {
|
|
43
|
+
width: auto;
|
|
44
|
+
height: 3;
|
|
45
|
+
content-align: center middle;
|
|
46
|
+
padding: 0 2;
|
|
47
|
+
border: round #666666;
|
|
48
|
+
color: $text;
|
|
49
|
+
background: $surface;
|
|
50
|
+
}
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
message: str | None = None,
|
|
56
|
+
*,
|
|
57
|
+
width: int = DEFAULT_WIDTH,
|
|
58
|
+
speed_ms: int = DEFAULT_SPEED_MS,
|
|
59
|
+
variant: str = "accent",
|
|
60
|
+
low_motion: bool = False,
|
|
61
|
+
**kwargs: Any,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Initialize the PulseIndicator."""
|
|
64
|
+
super().__init__(**kwargs)
|
|
65
|
+
self._width = self._coerce_width(width)
|
|
66
|
+
self._speed_ms = self._coerce_speed(speed_ms)
|
|
67
|
+
self._variant = self._coerce_variant(variant)
|
|
68
|
+
self._message = self._normalize_message(message)
|
|
69
|
+
self._low_motion = bool(low_motion)
|
|
70
|
+
self._position = 0
|
|
71
|
+
self._direction = 1
|
|
72
|
+
self._timer: Timer | None = None
|
|
73
|
+
self._pending_render: Text | None = None
|
|
74
|
+
self.can_focus = False
|
|
75
|
+
self.accessible_label = self._message
|
|
76
|
+
|
|
77
|
+
def on_mount(self) -> None:
|
|
78
|
+
"""Handle component mounting."""
|
|
79
|
+
# Initial render happens here to ensure component is ready for updates
|
|
80
|
+
self._safe_update(self._render_static() if self._low_motion else self._render_frame())
|
|
81
|
+
if self._pending_render is not None:
|
|
82
|
+
return
|
|
83
|
+
if self._timer is None and not self._low_motion:
|
|
84
|
+
self._timer = self.set_interval(self._speed_ms / 1000, self._tick)
|
|
85
|
+
|
|
86
|
+
def start(self, message: str | None = None) -> None:
|
|
87
|
+
"""Start the pulse animation."""
|
|
88
|
+
if message is not None:
|
|
89
|
+
self.update_message(message)
|
|
90
|
+
self._apply_pending_render()
|
|
91
|
+
self._cancel_timer()
|
|
92
|
+
if self._low_motion:
|
|
93
|
+
self._position = 0
|
|
94
|
+
self._safe_update(self._render_static())
|
|
95
|
+
return
|
|
96
|
+
self._timer = self.set_interval(self._speed_ms / 1000, self._tick)
|
|
97
|
+
self._safe_update(self._render_frame())
|
|
98
|
+
|
|
99
|
+
def stop(self, message: str | None = None) -> None:
|
|
100
|
+
"""Stop the pulse animation."""
|
|
101
|
+
if message is not None:
|
|
102
|
+
self.update_message(message)
|
|
103
|
+
self._cancel_timer()
|
|
104
|
+
self._position = 0
|
|
105
|
+
self._direction = 1
|
|
106
|
+
self._safe_update(self._render_static())
|
|
107
|
+
|
|
108
|
+
def update_message(self, message: str) -> None:
|
|
109
|
+
"""Update the display message."""
|
|
110
|
+
self._message = self._normalize_message(message)
|
|
111
|
+
self.accessible_label = self._message
|
|
112
|
+
self._safe_update(self._render_static() if self._low_motion else self._render_frame())
|
|
113
|
+
|
|
114
|
+
def _tick(self) -> None:
|
|
115
|
+
self._position += self._direction
|
|
116
|
+
if self._position >= self._width - 1:
|
|
117
|
+
self._position = self._width - 1
|
|
118
|
+
self._direction = -1
|
|
119
|
+
elif self._position <= 0:
|
|
120
|
+
self._position = 0
|
|
121
|
+
self._direction = 1
|
|
122
|
+
self._safe_update(self._render_frame())
|
|
123
|
+
|
|
124
|
+
def _render_frame(self) -> Text:
|
|
125
|
+
bar = self._render_bar(self._position, active=True)
|
|
126
|
+
bar.append(" ")
|
|
127
|
+
bar.append(self._message, style=self._message_style)
|
|
128
|
+
return bar
|
|
129
|
+
|
|
130
|
+
def _render_static(self) -> Text:
|
|
131
|
+
bar = self._render_bar(0, active=False)
|
|
132
|
+
bar.append(" ")
|
|
133
|
+
bar.append(self._message, style=self._message_style)
|
|
134
|
+
return bar
|
|
135
|
+
|
|
136
|
+
def _render_bar(self, position: int, *, active: bool) -> Text:
|
|
137
|
+
bg = self._resolve_style("on #111111", "$surface", is_bg=True)
|
|
138
|
+
bar = Text("[", style=f"grey37 {bg}")
|
|
139
|
+
|
|
140
|
+
p = position
|
|
141
|
+
v = self._active_style
|
|
142
|
+
|
|
143
|
+
for index in range(self._width):
|
|
144
|
+
if not active:
|
|
145
|
+
glyph = "█"
|
|
146
|
+
style = f"dim {v} {bg}"
|
|
147
|
+
else:
|
|
148
|
+
glyph, style = self._get_pulse_glyph_and_style(index, p, v, bg)
|
|
149
|
+
|
|
150
|
+
bar.append(glyph, style=style)
|
|
151
|
+
|
|
152
|
+
bar.append("]", style=f"grey37 {bg}")
|
|
153
|
+
return bar
|
|
154
|
+
|
|
155
|
+
def _get_pulse_glyph_and_style(self, index: int, p: int, v: str, bg: str) -> tuple[str, str]:
|
|
156
|
+
"""Determine glyph and style for a bar position during animation."""
|
|
157
|
+
dist = abs(index - p)
|
|
158
|
+
if dist == 0:
|
|
159
|
+
return "█", f"bold white {bg}"
|
|
160
|
+
if dist == 1:
|
|
161
|
+
return "█", f"{v} {bg}"
|
|
162
|
+
if dist == 2:
|
|
163
|
+
return "▓", f"dim {v} {bg}"
|
|
164
|
+
if dist == 3:
|
|
165
|
+
return "▒", f"dim {v} {bg}"
|
|
166
|
+
return " ", bg
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def _active_style(self) -> str:
|
|
170
|
+
token = f"${self._variant}"
|
|
171
|
+
fallback = VARIANT_STYLES.get(self._variant, VARIANT_STYLES["accent"])
|
|
172
|
+
return self._resolve_style(fallback, token)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def _message_style(self) -> str:
|
|
176
|
+
token = "$text-muted" if self._variant == "subtle" else "$text"
|
|
177
|
+
fallback = VARIANT_STYLES["subtle"] if self._variant == "subtle" else "white"
|
|
178
|
+
return self._resolve_style(fallback, token)
|
|
179
|
+
|
|
180
|
+
def _resolve_style(self, fallback: str, token: str | None = None, *, is_bg: bool = False) -> str:
|
|
181
|
+
"""Resolve a theme token to a Rich style string with fallback."""
|
|
182
|
+
try:
|
|
183
|
+
# Standard resolution sequence
|
|
184
|
+
res = self._do_resolve(token, is_bg)
|
|
185
|
+
if res:
|
|
186
|
+
return res
|
|
187
|
+
|
|
188
|
+
# Specific background resolution fallback
|
|
189
|
+
if is_bg:
|
|
190
|
+
res = self._do_resolve("$surface", True) or self._do_resolve("$background", True)
|
|
191
|
+
if res:
|
|
192
|
+
return res
|
|
193
|
+
except (NoActiveAppError, AttributeError):
|
|
194
|
+
pass
|
|
195
|
+
return fallback
|
|
196
|
+
|
|
197
|
+
def _do_resolve(self, token: str | None, is_bg: bool) -> str | None:
|
|
198
|
+
"""Internal resolver that tries multiple sources."""
|
|
199
|
+
if not token:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
# 1. Try resolving via component styles
|
|
203
|
+
if token.startswith("$"):
|
|
204
|
+
res = self._resolve_from_component(token, is_bg)
|
|
205
|
+
if res:
|
|
206
|
+
return res
|
|
207
|
+
|
|
208
|
+
# 2. Try direct variable lookup (App.theme_variables or Theme.variables)
|
|
209
|
+
res = self._resolve_from_theme_vars(token.lstrip("$"), is_bg)
|
|
210
|
+
if res:
|
|
211
|
+
return res
|
|
212
|
+
|
|
213
|
+
# 3. Try our built-in theme catalog
|
|
214
|
+
return self._resolve_from_catalog(token.lstrip("$"), is_bg)
|
|
215
|
+
|
|
216
|
+
def _resolve_from_component(self, token: str, is_bg: bool) -> str | None:
|
|
217
|
+
"""Resolve style from Textual component registry."""
|
|
218
|
+
try:
|
|
219
|
+
style = self.app.get_component_rich_style(token)
|
|
220
|
+
color = style.bgcolor if is_bg else style.color
|
|
221
|
+
if color:
|
|
222
|
+
return self._color_to_rich_style(color, is_bg)
|
|
223
|
+
except Exception:
|
|
224
|
+
pass
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def _resolve_from_theme_vars(self, var_name: str, is_bg: bool) -> str | None:
|
|
228
|
+
"""Resolve color from theme variables dictionary."""
|
|
229
|
+
try:
|
|
230
|
+
app = self.app
|
|
231
|
+
# Check theme_variables first
|
|
232
|
+
val = getattr(app, "theme_variables", {}).get(var_name)
|
|
233
|
+
if val is None:
|
|
234
|
+
# Fallback to current theme object's variables (Textual 0.52+)
|
|
235
|
+
theme_obj = app.get_theme(app.theme)
|
|
236
|
+
if theme_obj and hasattr(theme_obj, "variables"):
|
|
237
|
+
val = theme_obj.variables.get(var_name)
|
|
238
|
+
|
|
239
|
+
if val:
|
|
240
|
+
return self._color_to_rich_style(val, is_bg)
|
|
241
|
+
except Exception:
|
|
242
|
+
pass
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def _resolve_from_catalog(self, var_name: str, is_bg: bool) -> str | None:
|
|
246
|
+
"""Resolve color from our built-in theme catalog."""
|
|
247
|
+
try:
|
|
248
|
+
theme_name = getattr(self.app, "theme", "gl-dark")
|
|
249
|
+
theme_tokens = _BUILTIN_THEMES.get(theme_name, _BUILTIN_THEMES["gl-dark"])
|
|
250
|
+
val = getattr(theme_tokens, var_name.replace("-", "_"), None)
|
|
251
|
+
if val:
|
|
252
|
+
return self._color_to_rich_style(val, is_bg)
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def _color_to_rich_style(self, color: Any, is_bg: bool) -> str | None:
|
|
258
|
+
"""Convert any color-like object to a Rich-compatible style string."""
|
|
259
|
+
if not color:
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
# 1. Textual Color objects
|
|
263
|
+
if hasattr(color, "hex") and color.hex.startswith("#"):
|
|
264
|
+
return f"on {color.hex}" if is_bg else color.hex
|
|
265
|
+
|
|
266
|
+
# 2. Rich Color objects (with triplets)
|
|
267
|
+
if hasattr(color, "triplet") and color.triplet:
|
|
268
|
+
hex_val = color.triplet.hex
|
|
269
|
+
return f"on {hex_val}" if is_bg else hex_val
|
|
270
|
+
|
|
271
|
+
# 3. Strings or named colors
|
|
272
|
+
return self._str_color_to_style(color, is_bg)
|
|
273
|
+
|
|
274
|
+
def _str_color_to_style(self, color: Any, is_bg: bool) -> str | None:
|
|
275
|
+
"""Helper to convert string-based colors to style."""
|
|
276
|
+
if color is None:
|
|
277
|
+
return None
|
|
278
|
+
c_str = str(color).strip()
|
|
279
|
+
if not c_str:
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
if c_str.startswith("#"):
|
|
283
|
+
return f"on {c_str}" if is_bg else c_str
|
|
284
|
+
|
|
285
|
+
# If it's a named color like 'white', Rich understands it directly
|
|
286
|
+
# but we skip Textual's 'color(N)' internal format.
|
|
287
|
+
if not c_str.startswith("color(") and not c_str.startswith("auto"):
|
|
288
|
+
return f"on {c_str}" if is_bg else c_str
|
|
289
|
+
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
def _safe_update(self, renderable: Text) -> None:
|
|
293
|
+
try:
|
|
294
|
+
self.update(renderable)
|
|
295
|
+
self._pending_render = None
|
|
296
|
+
except NoActiveAppError:
|
|
297
|
+
self._pending_render = renderable
|
|
298
|
+
|
|
299
|
+
def _apply_pending_render(self) -> None:
|
|
300
|
+
if self._pending_render is None:
|
|
301
|
+
return
|
|
302
|
+
try:
|
|
303
|
+
self.update(self._pending_render)
|
|
304
|
+
self._pending_render = None
|
|
305
|
+
except NoActiveAppError:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
def _cancel_timer(self) -> None:
|
|
309
|
+
if self._timer is None:
|
|
310
|
+
return
|
|
311
|
+
try:
|
|
312
|
+
self._timer.stop()
|
|
313
|
+
except Exception:
|
|
314
|
+
pass
|
|
315
|
+
self._timer = None
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _normalize_message(message: str | None) -> str:
|
|
319
|
+
if message is None:
|
|
320
|
+
return DEFAULT_MESSAGE
|
|
321
|
+
cleaned = str(message).strip()
|
|
322
|
+
return cleaned if cleaned else DEFAULT_MESSAGE
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def _coerce_width(width: int) -> int:
|
|
326
|
+
if not isinstance(width, int):
|
|
327
|
+
return DEFAULT_WIDTH
|
|
328
|
+
return width if width > 0 else DEFAULT_WIDTH
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def _coerce_speed(speed_ms: int) -> int:
|
|
332
|
+
if not isinstance(speed_ms, int):
|
|
333
|
+
return DEFAULT_SPEED_MS
|
|
334
|
+
return speed_ms if speed_ms > 0 else DEFAULT_SPEED_MS
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def _coerce_variant(variant: str) -> str:
|
|
338
|
+
if not isinstance(variant, str):
|
|
339
|
+
return "accent"
|
|
340
|
+
normalized = variant.strip().lower()
|
|
341
|
+
return normalized if normalized in VARIANT_STYLES else "accent"
|
|
@@ -1,58 +1,80 @@
|
|
|
1
1
|
"""Shared helpers for toggling Textual loading indicators.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
This module provides unified helpers for showing/hiding both the built-in
|
|
4
|
+
Textual LoadingIndicator and the custom PulseIndicator.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
from collections.abc import Callable
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
from textual.widgets import LoadingIndicator
|
|
14
|
-
except Exception: # pragma: no cover - optional dependency
|
|
15
|
-
LoadingIndicator = None # type: ignore[assignment]
|
|
12
|
+
from textual.widgets import LoadingIndicator
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
from textual.widgets import LoadingIndicator as _LoadingIndicatorType
|
|
14
|
+
from glaip_sdk.cli.slash.tui.indicators import PulseIndicator
|
|
19
15
|
|
|
20
|
-
LoadingIndicator: type[_LoadingIndicatorType] | None
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
def _set_indicator_display(app: Any, selector: str, visible: bool) -> None:
|
|
18
|
+
try:
|
|
19
|
+
indicator = app.query_one(selector, PulseIndicator)
|
|
20
|
+
if visible:
|
|
21
|
+
indicator.display = True
|
|
22
|
+
indicator.start()
|
|
23
|
+
else:
|
|
24
|
+
indicator.stop()
|
|
25
|
+
indicator.display = False
|
|
26
26
|
return
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
|
|
27
30
|
try:
|
|
28
|
-
indicator = app.query_one(selector, LoadingIndicator)
|
|
31
|
+
indicator = app.query_one(selector, LoadingIndicator)
|
|
29
32
|
indicator.display = visible
|
|
30
33
|
except Exception:
|
|
31
|
-
# Ignore lookup/rendering errors to keep UI resilient
|
|
32
34
|
return
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def show_loading_indicator(
|
|
36
|
-
app:
|
|
38
|
+
app: Any,
|
|
37
39
|
selector: str,
|
|
38
40
|
*,
|
|
39
41
|
message: str | None = None,
|
|
40
42
|
set_status: Callable[..., None] | None = None,
|
|
41
43
|
status_style: str = "cyan",
|
|
42
44
|
) -> None:
|
|
43
|
-
"""Show a loading indicator and optionally set a status message.
|
|
45
|
+
"""Show a loading indicator (PulseIndicator or LoadingIndicator) and optionally set a status message.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
app: Textual app instance containing the indicator widget
|
|
49
|
+
selector: CSS selector for the indicator widget
|
|
50
|
+
message: Optional message to display in the indicator
|
|
51
|
+
set_status: Optional callback to set status message (for fallback display)
|
|
52
|
+
status_style: Style for status message if set_status is provided
|
|
53
|
+
"""
|
|
44
54
|
_set_indicator_display(app, selector, True)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
indicator = app.query_one(selector, PulseIndicator)
|
|
58
|
+
if message:
|
|
59
|
+
indicator.update_message(message)
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
45
63
|
if message and set_status:
|
|
46
64
|
try:
|
|
47
65
|
set_status(message, status_style)
|
|
48
66
|
except TypeError:
|
|
49
|
-
# Fallback for setters that accept only a single arg or kwargs
|
|
50
67
|
try:
|
|
51
68
|
set_status(message)
|
|
52
69
|
except Exception:
|
|
53
70
|
return
|
|
54
71
|
|
|
55
72
|
|
|
56
|
-
def hide_loading_indicator(app:
|
|
57
|
-
"""Hide a loading indicator.
|
|
73
|
+
def hide_loading_indicator(app: Any, selector: str) -> None:
|
|
74
|
+
"""Hide a loading indicator (PulseIndicator or LoadingIndicator).
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
app: Textual app instance containing the indicator widget
|
|
78
|
+
selector: CSS selector for the indicator widget
|
|
79
|
+
"""
|
|
58
80
|
_set_indicator_display(app, selector, False)
|
|
@@ -18,32 +18,17 @@ from typing import Any
|
|
|
18
18
|
|
|
19
19
|
from rich.text import Text
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
from textual.widgets import DataTable, Footer, Header, LoadingIndicator, RichLog, Static
|
|
29
|
-
except Exception: # pragma: no cover - optional dependency
|
|
30
|
-
App = None # type: ignore[assignment]
|
|
31
|
-
ComposeResult = None # type: ignore[assignment]
|
|
32
|
-
Binding = None # type: ignore[assignment]
|
|
33
|
-
Horizontal = None # type: ignore[assignment]
|
|
34
|
-
Vertical = None # type: ignore[assignment]
|
|
35
|
-
Coordinate = None # type: ignore[assignment]
|
|
36
|
-
ReactiveError = Exception # type: ignore[assignment, misc]
|
|
37
|
-
ModalScreen = object # type: ignore[assignment, misc]
|
|
38
|
-
DataTable = None # type: ignore[assignment]
|
|
39
|
-
Footer = None # type: ignore[assignment]
|
|
40
|
-
Header = None # type: ignore[assignment]
|
|
41
|
-
LoadingIndicator = None # type: ignore[assignment]
|
|
42
|
-
RichLog = None # type: ignore[assignment]
|
|
43
|
-
Static = None # type: ignore[assignment]
|
|
21
|
+
from textual.app import App, ComposeResult
|
|
22
|
+
from textual.binding import Binding
|
|
23
|
+
from textual.containers import Horizontal, Vertical
|
|
24
|
+
from textual.coordinate import Coordinate
|
|
25
|
+
from textual.reactive import ReactiveError
|
|
26
|
+
from textual.screen import ModalScreen
|
|
27
|
+
from textual.widgets import DataTable, Footer, Header, RichLog, Static
|
|
44
28
|
|
|
45
29
|
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter
|
|
46
30
|
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
31
|
+
from glaip_sdk.cli.slash.tui.indicators import PulseIndicator
|
|
47
32
|
from glaip_sdk.cli.slash.tui.loading import hide_loading_indicator, show_loading_indicator
|
|
48
33
|
from glaip_sdk.cli.slash.tui.toast import ClipboardToastMixin, Toast, ToastBus, ToastContainer, ToastHandlerMixin
|
|
49
34
|
|
|
@@ -327,7 +312,6 @@ class RemoteRunsTextualApp(ToastHandlerMixin, App[None]):
|
|
|
327
312
|
"""Textual application for browsing remote runs."""
|
|
328
313
|
|
|
329
314
|
CSS = f"""
|
|
330
|
-
Screen {{ layout: vertical; layers: base toasts; }}
|
|
331
315
|
#toast-container {{
|
|
332
316
|
width: 100%;
|
|
333
317
|
height: auto;
|
|
@@ -335,10 +319,14 @@ class RemoteRunsTextualApp(ToastHandlerMixin, App[None]):
|
|
|
335
319
|
align: right top;
|
|
336
320
|
layer: toasts;
|
|
337
321
|
}}
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
322
|
+
#{RUNS_LOADING_ID} {{
|
|
323
|
+
width: auto;
|
|
324
|
+
display: none;
|
|
325
|
+
}}
|
|
326
|
+
#status-bar {{
|
|
327
|
+
height: 3;
|
|
328
|
+
padding: 0 1;
|
|
329
|
+
}}
|
|
342
330
|
"""
|
|
343
331
|
|
|
344
332
|
BINDINGS = [
|
|
@@ -400,7 +388,7 @@ class RemoteRunsTextualApp(ToastHandlerMixin, App[None]):
|
|
|
400
388
|
)
|
|
401
389
|
yield table # pragma: no cover - interactive UI, tested via integration
|
|
402
390
|
yield Horizontal( # pragma: no cover - interactive UI, tested via integration
|
|
403
|
-
|
|
391
|
+
PulseIndicator(id=RUNS_LOADING_ID),
|
|
404
392
|
Static(id="status"),
|
|
405
393
|
id="status-bar",
|
|
406
394
|
)
|
|
@@ -557,7 +545,7 @@ class RemoteRunsTextualApp(ToastHandlerMixin, App[None]):
|
|
|
557
545
|
self._update_status("Already loading run detail. Please wait…", append=True)
|
|
558
546
|
return
|
|
559
547
|
run_id = str(run.id)
|
|
560
|
-
self._show_loading("Loading run detail…", table_spinner=False)
|
|
548
|
+
self._show_loading("Loading run detail…", table_spinner=False, footer_message=False)
|
|
561
549
|
self._queue_detail_load(run_id)
|
|
562
550
|
|
|
563
551
|
async def action_export_run(self) -> None:
|
|
@@ -750,7 +738,7 @@ class RemoteRunsTextualApp(ToastHandlerMixin, App[None]):
|
|
|
750
738
|
show_loading_indicator(
|
|
751
739
|
self,
|
|
752
740
|
RUNS_LOADING_SELECTOR,
|
|
753
|
-
message=message
|
|
741
|
+
message=message,
|
|
754
742
|
set_status=self._update_status if footer_message else None,
|
|
755
743
|
)
|
|
756
744
|
self._set_table_loading(table_spinner)
|
glaip_sdk/runner/deps.py
CHANGED
|
@@ -31,7 +31,10 @@ def _probe_aip_agents_import() -> bool:
|
|
|
31
31
|
Returns:
|
|
32
32
|
True if aip_agents appears importable, False otherwise.
|
|
33
33
|
"""
|
|
34
|
-
|
|
34
|
+
try:
|
|
35
|
+
return importlib.util.find_spec("aip_agents") is not None
|
|
36
|
+
except (ImportError, ValueError):
|
|
37
|
+
return False
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def check_local_runtime_available() -> bool:
|
|
@@ -78,16 +78,17 @@ glaip_sdk/cli/slash/accounts_shared.py,sha256=Mq5HxlI0YsVEQ0KKISWvyBZhzOFFWCzwRb
|
|
|
78
78
|
glaip_sdk/cli/slash/agent_session.py,sha256=tuVOme-NbEyr6rwJvsBEKZYWQmsaRf4piJeRvIGu0ns,11384
|
|
79
79
|
glaip_sdk/cli/slash/prompt.py,sha256=q4f1c2zr7ZMUeO6AgOBF2Nz4qgMOXrVPt6WzPRQMbAM,8501
|
|
80
80
|
glaip_sdk/cli/slash/remote_runs_controller.py,sha256=iLl4a-mu9QU7dcedgEILewPtDIVtFUJkbKGtcx1F66U,21445
|
|
81
|
-
glaip_sdk/cli/slash/session.py,sha256=
|
|
82
|
-
glaip_sdk/cli/slash/tui/__init__.py,sha256=
|
|
83
|
-
glaip_sdk/cli/slash/tui/accounts.tcss,sha256=
|
|
84
|
-
glaip_sdk/cli/slash/tui/accounts_app.py,sha256=
|
|
81
|
+
glaip_sdk/cli/slash/session.py,sha256=jWTPrt374tDTt3tN-nBQ5wb2ssc60yMSAcSp4FNej2Y,76308
|
|
82
|
+
glaip_sdk/cli/slash/tui/__init__.py,sha256=hAjH4ULBhRpQzA6fBLWRV6LiVm8UM3lgPurjoX9muYU,1061
|
|
83
|
+
glaip_sdk/cli/slash/tui/accounts.tcss,sha256=5iVZZfS10CTJhnoZ9AFJejtj8nyQXH9xV7u9k8jSkGE,2411
|
|
84
|
+
glaip_sdk/cli/slash/tui/accounts_app.py,sha256=5FtQy57xdUtBfOVRsqjXSbWLsLna7wIoMOz9t3h_ptQ,73187
|
|
85
85
|
glaip_sdk/cli/slash/tui/background_tasks.py,sha256=SAe1mV2vXB3mJcSGhelU950vf8Lifjhws9iomyIVFKw,2422
|
|
86
86
|
glaip_sdk/cli/slash/tui/clipboard.py,sha256=7fEshhTwHYaj-n7n0W0AsWTs8W0RLZw_9luXxrFTrtw,6227
|
|
87
87
|
glaip_sdk/cli/slash/tui/context.py,sha256=mzI4TDXnfZd42osACp5uo10d10y1_A0z6IxRK1KVoVk,3320
|
|
88
|
+
glaip_sdk/cli/slash/tui/indicators.py,sha256=jV3fFvEVWQ0inWJJ-B1fMsdkF0Uq2zwX3xcl0YWPHSE,11768
|
|
88
89
|
glaip_sdk/cli/slash/tui/keybind_registry.py,sha256=_rK05BxTxNudYc4iJ9gDxpgeUkjDAq8rarIT-9A-jyM,6739
|
|
89
|
-
glaip_sdk/cli/slash/tui/loading.py,sha256=
|
|
90
|
-
glaip_sdk/cli/slash/tui/remote_runs_app.py,sha256=
|
|
90
|
+
glaip_sdk/cli/slash/tui/loading.py,sha256=Ku7HyQ_h-r2dJQ5aIEaCOi5PUu5gSsYle8oiKHIxfKI,2336
|
|
91
|
+
glaip_sdk/cli/slash/tui/remote_runs_app.py,sha256=MVbzx-VJGi-wGSzr_CjQbdZbcWboVEbrCZUCuH2AeJM,29388
|
|
91
92
|
glaip_sdk/cli/slash/tui/terminal.py,sha256=ZAC3sB17TGpl-GFeRVm_nI8DQTN3pyti3ynlZ41wT_A,12323
|
|
92
93
|
glaip_sdk/cli/slash/tui/toast.py,sha256=XGITLHhO40xIGmtg9hanPmDsPCQY2hQXzoM_9mJXQyg,12442
|
|
93
94
|
glaip_sdk/cli/slash/tui/layouts/__init__.py,sha256=KT77pZHa7Wz84QlHYT2mfhQ_AXUA-T0eHv_HtAvc1ac,473
|
|
@@ -150,7 +151,7 @@ glaip_sdk/registry/mcp.py,sha256=kNJmiijIbZL9Btx5o2tFtbaT-WG6O4Xf_nl3wz356Ow,797
|
|
|
150
151
|
glaip_sdk/registry/tool.py,sha256=c0Ja4rFYMOKs_1yjDLDZxCId4IjQzprwXzX0iIL8Fio,14979
|
|
151
152
|
glaip_sdk/runner/__init__.py,sha256=orJ3nLR9P-n1qMaAMWZ_xRS4368YnDpdltg-bX5BlUk,2210
|
|
152
153
|
glaip_sdk/runner/base.py,sha256=KIjcSAyDCP9_mn2H4rXR5gu1FZlwD9pe0gkTBmr6Yi4,2663
|
|
153
|
-
glaip_sdk/runner/deps.py,sha256=
|
|
154
|
+
glaip_sdk/runner/deps.py,sha256=Lv8LdIF6H4JGzzvLmi-MgG72RJYgB-MsQNRx8yY7cl4,3956
|
|
154
155
|
glaip_sdk/runner/langgraph.py,sha256=HB1n6w44LdyE7OSB2iSEtwhm_IeD7fGi_20erCZurX4,40869
|
|
155
156
|
glaip_sdk/runner/logging_config.py,sha256=OrQgW23t42qQRqEXKH8U4bFg4JG5EEkUJTlbvtU65iE,2528
|
|
156
157
|
glaip_sdk/runner/mcp_adapter/__init__.py,sha256=Rdttfg3N6kg3-DaTCKqaGXKByZyBt0Mwf6FV8s_5kI8,462
|
|
@@ -216,8 +217,8 @@ glaip_sdk/utils/rendering/steps/format.py,sha256=Chnq7OBaj8XMeBntSBxrX5zSmrYeGcO
|
|
|
216
217
|
glaip_sdk/utils/rendering/steps/manager.py,sha256=BiBmTeQMQhjRMykgICXsXNYh1hGsss-fH9BIGVMWFi0,13194
|
|
217
218
|
glaip_sdk/utils/rendering/viewer/__init__.py,sha256=XrxmE2cMAozqrzo1jtDFm8HqNtvDcYi2mAhXLXn5CjI,457
|
|
218
219
|
glaip_sdk/utils/rendering/viewer/presenter.py,sha256=mlLMTjnyeyPVtsyrAbz1BJu9lFGQSlS-voZ-_Cuugv0,5725
|
|
219
|
-
glaip_sdk-0.7.
|
|
220
|
-
glaip_sdk-0.7.
|
|
221
|
-
glaip_sdk-0.7.
|
|
222
|
-
glaip_sdk-0.7.
|
|
223
|
-
glaip_sdk-0.7.
|
|
220
|
+
glaip_sdk-0.7.16.dist-info/METADATA,sha256=oqaUXPHF7LzD6CCvuJoHe10WD8OMhzMlKOKSgqwpx-E,8528
|
|
221
|
+
glaip_sdk-0.7.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
222
|
+
glaip_sdk-0.7.16.dist-info/entry_points.txt,sha256=NkhO6FfgX9Zrjn63GuKphf-dLw7KNJvucAcXc7P3aMk,54
|
|
223
|
+
glaip_sdk-0.7.16.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
|
|
224
|
+
glaip_sdk-0.7.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|