glaip-sdk 0.1.2__py3-none-any.whl → 0.7.17__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/__init__.py +44 -4
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1413 -0
- glaip_sdk/branding.py +126 -2
- glaip_sdk/cli/account_store.py +555 -0
- glaip_sdk/cli/auth.py +260 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +728 -113
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +12 -8
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +49 -4
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +41 -20
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +340 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +12 -13
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +568 -0
- glaip_sdk/cli/slash/session.py +1105 -153
- glaip_sdk/cli/slash/tui/__init__.py +36 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +177 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +1853 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
- glaip_sdk/cli/slash/tui/loading.py +80 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +760 -0
- glaip_sdk/cli/slash/tui/terminal.py +407 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +388 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +66 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -463
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +227 -10
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +576 -44
- glaip_sdk/client/base.py +26 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -14
- glaip_sdk/client/mcps.py +165 -24
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +63 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +546 -92
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +206 -32
- glaip_sdk/config/constants.py +33 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +136 -0
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +48 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +1055 -0
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +116 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +488 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- glaip_sdk/utils/import_resolver.py +524 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +299 -1434
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -33
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +25 -13
- glaip_sdk/utils/runtime_config.py +426 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +69 -23
- glaip_sdk-0.7.17.dist-info/RECORD +224 -0
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1369
- glaip_sdk/cli/commands/mcps.py +0 -1187
- glaip_sdk/cli/commands/tools.py +0 -584
- glaip_sdk/cli/utils.py +0 -1278
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.2.dist-info/RECORD +0 -82
- glaip_sdk-0.1.2.dist-info/entry_points.txt +0 -3
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Toast widgets and state management for the TUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, cast
|
|
10
|
+
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
from textual.message import Message
|
|
13
|
+
from textual.widget import Widget
|
|
14
|
+
from textual.widgets import Static
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToastVariant(str, Enum):
|
|
18
|
+
"""Toast message variant for styling and behavior."""
|
|
19
|
+
|
|
20
|
+
INFO = "info"
|
|
21
|
+
SUCCESS = "success"
|
|
22
|
+
WARNING = "warning"
|
|
23
|
+
ERROR = "error"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEFAULT_TOAST_DURATIONS_SECONDS: dict[ToastVariant, float] = {
|
|
27
|
+
ToastVariant.SUCCESS: 2.0,
|
|
28
|
+
ToastVariant.INFO: 3.0,
|
|
29
|
+
ToastVariant.WARNING: 3.0,
|
|
30
|
+
ToastVariant.ERROR: 5.0,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class ToastState:
|
|
36
|
+
"""Immutable toast notification state."""
|
|
37
|
+
|
|
38
|
+
message: str
|
|
39
|
+
variant: ToastVariant
|
|
40
|
+
duration_seconds: float
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ToastBus:
|
|
44
|
+
"""Toast state manager with auto-dismiss functionality."""
|
|
45
|
+
|
|
46
|
+
class Changed(Message):
|
|
47
|
+
"""Message sent when toast state changes."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, state: ToastState | None) -> None:
|
|
50
|
+
"""Initialize the changed message with new toast state."""
|
|
51
|
+
super().__init__()
|
|
52
|
+
self.state = state
|
|
53
|
+
|
|
54
|
+
def __init__(self, on_change: Callable[[ToastBus.Changed], None] | None = None) -> None:
|
|
55
|
+
"""Initialize the toast bus with optional change callback."""
|
|
56
|
+
self._state: ToastState | None = None
|
|
57
|
+
self._dismiss_task: asyncio.Task[None] | None = None
|
|
58
|
+
self._on_change = on_change
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def state(self) -> ToastState | None:
|
|
62
|
+
"""Return the current toast state, or None if no toast is shown."""
|
|
63
|
+
return self._state
|
|
64
|
+
|
|
65
|
+
def show(
|
|
66
|
+
self,
|
|
67
|
+
message: str,
|
|
68
|
+
variant: ToastVariant | str = ToastVariant.INFO,
|
|
69
|
+
*,
|
|
70
|
+
duration_seconds: float | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Show a toast notification with the given message and variant.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
message: The message to display in the toast.
|
|
76
|
+
variant: The visual variant of the toast (INFO, SUCCESS, WARNING, ERROR).
|
|
77
|
+
duration_seconds: Optional custom duration in seconds. If None, uses default
|
|
78
|
+
duration for the variant (2s for SUCCESS, 3s for INFO/WARNING, 5s for ERROR).
|
|
79
|
+
"""
|
|
80
|
+
resolved_variant = self._coerce_variant(variant)
|
|
81
|
+
resolved_duration = (
|
|
82
|
+
DEFAULT_TOAST_DURATIONS_SECONDS[resolved_variant] if duration_seconds is None else float(duration_seconds)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self._state = ToastState(
|
|
86
|
+
message=message,
|
|
87
|
+
variant=resolved_variant,
|
|
88
|
+
duration_seconds=resolved_duration,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self._cancel_dismiss_task()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
loop = asyncio.get_running_loop()
|
|
95
|
+
except RuntimeError:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
"Cannot schedule toast auto-dismiss: no running event loop. "
|
|
98
|
+
"ToastBus.show() must be called from within an async context."
|
|
99
|
+
) from None
|
|
100
|
+
|
|
101
|
+
self._dismiss_task = loop.create_task(self._auto_dismiss(resolved_duration))
|
|
102
|
+
self._notify_changed()
|
|
103
|
+
|
|
104
|
+
def clear(self) -> None:
|
|
105
|
+
"""Clear the current toast notification immediately."""
|
|
106
|
+
self._cancel_dismiss_task()
|
|
107
|
+
self._state = None
|
|
108
|
+
self._notify_changed()
|
|
109
|
+
|
|
110
|
+
def copy_success(self, label: str | None = None) -> None:
|
|
111
|
+
"""Show a success toast for clipboard copy operations.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
label: Optional label for what was copied (e.g., "Run ID", "JSON").
|
|
115
|
+
"""
|
|
116
|
+
message = "Copied to clipboard" if not label else f"Copied {label} to clipboard"
|
|
117
|
+
self.show(message=message, variant=ToastVariant.SUCCESS)
|
|
118
|
+
|
|
119
|
+
def copy_failed(self) -> None:
|
|
120
|
+
"""Show a warning toast when clipboard copy fails."""
|
|
121
|
+
self.show(message="Clipboard unavailable. Text printed below.", variant=ToastVariant.WARNING)
|
|
122
|
+
|
|
123
|
+
def _coerce_variant(self, variant: ToastVariant | str) -> ToastVariant:
|
|
124
|
+
if isinstance(variant, ToastVariant):
|
|
125
|
+
return variant
|
|
126
|
+
try:
|
|
127
|
+
return ToastVariant(variant)
|
|
128
|
+
except ValueError:
|
|
129
|
+
return ToastVariant.INFO
|
|
130
|
+
|
|
131
|
+
def _cancel_dismiss_task(self) -> None:
|
|
132
|
+
if self._dismiss_task is None:
|
|
133
|
+
return
|
|
134
|
+
if not self._dismiss_task.done():
|
|
135
|
+
self._dismiss_task.cancel()
|
|
136
|
+
self._dismiss_task = None
|
|
137
|
+
|
|
138
|
+
async def _auto_dismiss(self, duration_seconds: float) -> None:
|
|
139
|
+
try:
|
|
140
|
+
await asyncio.sleep(duration_seconds)
|
|
141
|
+
except asyncio.CancelledError:
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
self._state = None
|
|
145
|
+
self._dismiss_task = None
|
|
146
|
+
self._notify_changed()
|
|
147
|
+
|
|
148
|
+
def _notify_changed(self) -> None:
|
|
149
|
+
if self._on_change:
|
|
150
|
+
self._on_change(ToastBus.Changed(self._state))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class ToastHandlerMixin:
|
|
154
|
+
"""Mixin providing common toast handling functionality.
|
|
155
|
+
|
|
156
|
+
Classes that inherit from this mixin can handle ToastBus.Changed messages
|
|
157
|
+
by automatically updating all Toast widgets in the component tree.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def on_toast_bus_changed(self, message: ToastBus.Changed) -> None:
|
|
161
|
+
"""Refresh the toast widget when the toast bus updates.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
message: The toast bus changed message containing the new state.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
for toast in self.query(Toast):
|
|
168
|
+
toast.update_state(message.state)
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ClipboardToastMixin:
|
|
174
|
+
"""Mixin providing clipboard and toast orchestration functionality.
|
|
175
|
+
|
|
176
|
+
Classes that inherit from this mixin get shared clipboard adapter selection,
|
|
177
|
+
OSC52 writer setup, toast bus lookup, and copy-success/failure orchestration.
|
|
178
|
+
This consolidates duplicate clipboard/toast logic across TUI apps.
|
|
179
|
+
|
|
180
|
+
Expected attributes:
|
|
181
|
+
_ctx: TUIContext | None - Shared TUI context (optional)
|
|
182
|
+
_clipboard: ClipboardAdapter | None - Cached clipboard adapter (optional)
|
|
183
|
+
_local_toasts: ToastBus | None - Local toast bus instance (optional)
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def _clipboard_adapter(self) -> Any: # ClipboardAdapter
|
|
187
|
+
"""Get or create a clipboard adapter instance.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
ClipboardAdapter instance, preferring context's adapter if available.
|
|
191
|
+
"""
|
|
192
|
+
# Import here to avoid circular dependency
|
|
193
|
+
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter # noqa: PLC0415
|
|
194
|
+
|
|
195
|
+
ctx = getattr(self, "_ctx", None)
|
|
196
|
+
clipboard = getattr(self, "_clipboard", None)
|
|
197
|
+
|
|
198
|
+
if ctx is not None and ctx.clipboard is not None:
|
|
199
|
+
return cast(ClipboardAdapter, ctx.clipboard)
|
|
200
|
+
if clipboard is not None:
|
|
201
|
+
return clipboard
|
|
202
|
+
|
|
203
|
+
adapter = ClipboardAdapter(terminal=ctx.terminal if ctx else None)
|
|
204
|
+
if ctx is not None:
|
|
205
|
+
ctx.clipboard = adapter
|
|
206
|
+
else:
|
|
207
|
+
self._clipboard = adapter
|
|
208
|
+
return adapter
|
|
209
|
+
|
|
210
|
+
def _osc52_writer(self) -> Callable[[str], Any] | None:
|
|
211
|
+
"""Get an OSC52 writer function if console output is available.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Writer function that writes OSC52 sequences to console output, or None.
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
# Try self.app.console first (for Screen subclasses)
|
|
218
|
+
if hasattr(self, "app") and hasattr(self.app, "console"):
|
|
219
|
+
console = self.app.console
|
|
220
|
+
# Fall back to self.console (for App subclasses)
|
|
221
|
+
else:
|
|
222
|
+
console = getattr(self, "console", None)
|
|
223
|
+
except Exception:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
if console is None:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
output = getattr(console, "file", None)
|
|
230
|
+
if output is None:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
def _write(sequence: str, _output: Any = output) -> None:
|
|
234
|
+
_output.write(sequence)
|
|
235
|
+
_output.flush()
|
|
236
|
+
|
|
237
|
+
return _write
|
|
238
|
+
|
|
239
|
+
def _toast_bus(self) -> ToastBus | None:
|
|
240
|
+
"""Get the toast bus instance.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
ToastBus instance, preferring context's bus if available, or None.
|
|
244
|
+
"""
|
|
245
|
+
local_toasts = getattr(self, "_local_toasts", None)
|
|
246
|
+
ctx = getattr(self, "_ctx", None)
|
|
247
|
+
|
|
248
|
+
if local_toasts is not None:
|
|
249
|
+
return local_toasts
|
|
250
|
+
if ctx is not None and ctx.toasts is not None:
|
|
251
|
+
return ctx.toasts
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
def _copy_to_clipboard(self, text: str, *, label: str | None = None) -> None:
|
|
255
|
+
"""Copy text to clipboard and show toast notification.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
text: The text to copy to clipboard.
|
|
259
|
+
label: Optional label for what was copied (e.g., "Run ID", "JSON").
|
|
260
|
+
"""
|
|
261
|
+
adapter = self._clipboard_adapter()
|
|
262
|
+
writer = self._osc52_writer()
|
|
263
|
+
if writer:
|
|
264
|
+
result = adapter.copy(text, writer=writer)
|
|
265
|
+
else:
|
|
266
|
+
result = adapter.copy(text)
|
|
267
|
+
|
|
268
|
+
toasts = self._toast_bus()
|
|
269
|
+
if result.success:
|
|
270
|
+
if toasts:
|
|
271
|
+
toasts.copy_success(label)
|
|
272
|
+
else:
|
|
273
|
+
# Fallback to status announcement if toast bus unavailable
|
|
274
|
+
if hasattr(self, "_announce_status"):
|
|
275
|
+
if label:
|
|
276
|
+
self._announce_status(f"Copied {label} to clipboard.")
|
|
277
|
+
else:
|
|
278
|
+
self._announce_status("Copied to clipboard.")
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
# Copy failed
|
|
282
|
+
if toasts:
|
|
283
|
+
toasts.copy_failed()
|
|
284
|
+
else:
|
|
285
|
+
# Fallback to status announcement if toast bus unavailable
|
|
286
|
+
if hasattr(self, "_announce_status"):
|
|
287
|
+
self._announce_status("Clipboard unavailable. Text printed below.")
|
|
288
|
+
|
|
289
|
+
# Append fallback text output
|
|
290
|
+
if hasattr(self, "_append_copy_fallback"):
|
|
291
|
+
self._append_copy_fallback(text)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class ToastContainer(Widget):
|
|
295
|
+
"""Simple wrapper for docking toast widgets without relying on containers.
|
|
296
|
+
|
|
297
|
+
This class exists to provide a lightweight widget wrapper for toast containers
|
|
298
|
+
that avoids direct dependency on Textual's Container class. It allows the toast
|
|
299
|
+
system to work consistently across different Textual versions and provides a
|
|
300
|
+
stable API for toast container composition.
|
|
301
|
+
|
|
302
|
+
Usage:
|
|
303
|
+
yield ToastContainer(Toast(), id="toast-container")
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class Toast(Static):
|
|
308
|
+
"""A Textual widget that displays toast notifications at the top-right of the screen.
|
|
309
|
+
|
|
310
|
+
The Toast widget is updated via `update_state()` calls from message handlers
|
|
311
|
+
(e.g., `on_toast_bus_changed`). The widget does not auto-subscribe to ToastBus
|
|
312
|
+
state changes; the app must call `update_state()` when toast state changes.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
DEFAULT_CSS = """
|
|
316
|
+
#toast-container {
|
|
317
|
+
width: 100%;
|
|
318
|
+
height: auto;
|
|
319
|
+
dock: top;
|
|
320
|
+
align: right top;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
Toast {
|
|
324
|
+
width: auto;
|
|
325
|
+
min-width: 20;
|
|
326
|
+
max-width: 40;
|
|
327
|
+
height: auto;
|
|
328
|
+
padding: 0 1;
|
|
329
|
+
margin: 1 2;
|
|
330
|
+
background: $surface;
|
|
331
|
+
color: $text;
|
|
332
|
+
border: solid $primary;
|
|
333
|
+
display: none;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
Toast.visible {
|
|
337
|
+
display: block;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
Toast.info {
|
|
341
|
+
border: solid $accent;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
Toast.success {
|
|
345
|
+
border: solid $success;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Toast.warning {
|
|
349
|
+
border: solid $warning;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
Toast.error {
|
|
353
|
+
border: solid $error;
|
|
354
|
+
}
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
def __init__(self) -> None:
|
|
358
|
+
"""Initialize the Toast widget.
|
|
359
|
+
|
|
360
|
+
The widget is updated via `update_state()` calls from message handlers
|
|
361
|
+
(e.g., `on_toast_bus_changed`). The widget does not auto-subscribe to
|
|
362
|
+
a ToastBus; the app must call `update_state()` when toast state changes.
|
|
363
|
+
"""
|
|
364
|
+
super().__init__("")
|
|
365
|
+
|
|
366
|
+
def update_state(self, state: ToastState | None) -> None:
|
|
367
|
+
"""Update the toast display based on the provided state.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
state: The toast state to display, or None to hide the toast.
|
|
371
|
+
"""
|
|
372
|
+
if not state:
|
|
373
|
+
self.remove_class("visible")
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
icon = "ℹ️"
|
|
377
|
+
if state.variant == ToastVariant.SUCCESS:
|
|
378
|
+
icon = "✅"
|
|
379
|
+
elif state.variant == ToastVariant.WARNING:
|
|
380
|
+
icon = "⚠️"
|
|
381
|
+
elif state.variant == ToastVariant.ERROR:
|
|
382
|
+
icon = "❌"
|
|
383
|
+
|
|
384
|
+
self.update(Text.assemble((f"{icon} ", "bold"), state.message))
|
|
385
|
+
|
|
386
|
+
self.remove_class("info", "success", "warning", "error")
|
|
387
|
+
self.add_class(state.variant.value)
|
|
388
|
+
self.add_class("visible")
|
|
@@ -4,68 +4,28 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from glaip_sdk.cli.transcript.cache import (
|
|
8
|
-
TranscriptCacheStats,
|
|
9
|
-
TranscriptPayload,
|
|
10
|
-
TranscriptStoreResult,
|
|
11
|
-
ensure_cache_dir,
|
|
12
|
-
get_transcript_cache_stats,
|
|
13
|
-
latest_manifest_entry,
|
|
14
|
-
manifest_path,
|
|
15
|
-
resolve_manifest_entry,
|
|
16
|
-
store_transcript,
|
|
17
|
-
suggest_filename,
|
|
18
|
-
)
|
|
19
7
|
from glaip_sdk.cli.transcript.cache import (
|
|
20
8
|
export_transcript as export_cached_transcript,
|
|
21
9
|
)
|
|
22
|
-
from glaip_sdk.cli.transcript.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
coerce_result_text,
|
|
26
|
-
compute_finished_at,
|
|
27
|
-
extract_server_run_id,
|
|
28
|
-
register_last_transcript,
|
|
29
|
-
store_transcript_for_session,
|
|
10
|
+
from glaip_sdk.cli.transcript.cache import (
|
|
11
|
+
get_transcript_cache_stats,
|
|
12
|
+
suggest_filename,
|
|
30
13
|
)
|
|
14
|
+
from glaip_sdk.cli.transcript.capture import store_transcript_for_session
|
|
31
15
|
from glaip_sdk.cli.transcript.export import (
|
|
32
16
|
normalise_export_destination,
|
|
33
17
|
resolve_manifest_for_export,
|
|
34
18
|
)
|
|
35
|
-
from glaip_sdk.cli.transcript.
|
|
36
|
-
|
|
37
|
-
should_launch_post_run_viewer,
|
|
38
|
-
)
|
|
39
|
-
from glaip_sdk.cli.transcript.viewer import (
|
|
40
|
-
PostRunViewer,
|
|
41
|
-
ViewerContext,
|
|
42
|
-
run_viewer_session,
|
|
43
|
-
)
|
|
19
|
+
from glaip_sdk.cli.transcript.history import load_history_snapshot
|
|
20
|
+
from glaip_sdk.cli.transcript.launcher import maybe_launch_post_run_viewer
|
|
44
21
|
|
|
45
22
|
__all__ = [
|
|
46
|
-
"TranscriptCacheStats",
|
|
47
|
-
"TranscriptPayload",
|
|
48
|
-
"TranscriptStoreResult",
|
|
49
|
-
"ensure_cache_dir",
|
|
50
|
-
"get_transcript_cache_stats",
|
|
51
|
-
"manifest_path",
|
|
52
|
-
"store_transcript",
|
|
53
|
-
"suggest_filename",
|
|
54
|
-
"latest_manifest_entry",
|
|
55
|
-
"resolve_manifest_entry",
|
|
56
23
|
"export_cached_transcript",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"coerce_result_text",
|
|
60
|
-
"compute_finished_at",
|
|
61
|
-
"extract_server_run_id",
|
|
62
|
-
"register_last_transcript",
|
|
63
|
-
"store_transcript_for_session",
|
|
64
|
-
"resolve_manifest_for_export",
|
|
65
|
-
"normalise_export_destination",
|
|
24
|
+
"get_transcript_cache_stats",
|
|
25
|
+
"load_history_snapshot",
|
|
66
26
|
"maybe_launch_post_run_viewer",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
27
|
+
"normalise_export_destination",
|
|
28
|
+
"resolve_manifest_for_export",
|
|
29
|
+
"store_transcript_for_session",
|
|
30
|
+
"suggest_filename",
|
|
71
31
|
]
|