glaip-sdk 0.6.19__py3-none-any.whl → 0.7.27__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/agents/base.py +283 -30
- glaip_sdk/agents/component.py +233 -0
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- 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 +1 -1
- glaip_sdk/cli/commands/configure.py +1 -2
- 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 +2 -4
- 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.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +112 -35
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +3 -1
- glaip_sdk/cli/slash/agent_session.py +1 -1
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +343 -20
- glaip_sdk/cli/slash/tui/__init__.py +29 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
- glaip_sdk/cli/slash/tui/clipboard.py +316 -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 +43 -21
- glaip_sdk/cli/slash/tui/remote_runs_app.py +178 -20
- 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/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +1 -1
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +293 -17
- glaip_sdk/client/base.py +25 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -5
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +109 -30
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +52 -23
- glaip_sdk/config/constants.py +22 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +91 -0
- glaip_sdk/hitl/__init__.py +35 -2
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +1 -31
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +47 -1
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +2 -1
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/ptc.py +145 -0
- glaip_sdk/registry/tool.py +270 -57
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +4 -1
- glaip_sdk/runner/langgraph.py +251 -27
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
- glaip_sdk/runner/ptc_adapter.py +98 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +427 -49
- glaip_sdk/utils/runtime_config.py +3 -2
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +22 -8
- glaip_sdk-0.7.27.dist-info/RECORD +227 -0
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +1 -1
- glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -0
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.19.dist-info/RECORD +0 -163
- glaip_sdk-0.6.19.dist-info/entry_points.txt +0 -2
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Clipboard adapter for TUI copy actions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
|
|
16
|
+
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities, detect_osc52_support
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClipboardMethod(str, Enum):
|
|
20
|
+
"""Supported clipboard backends."""
|
|
21
|
+
|
|
22
|
+
OSC52 = "osc52"
|
|
23
|
+
PBCOPY = "pbcopy"
|
|
24
|
+
XCLIP = "xclip"
|
|
25
|
+
XSEL = "xsel"
|
|
26
|
+
WL_COPY = "wl-copy"
|
|
27
|
+
CLIP = "clip"
|
|
28
|
+
NONE = "none"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class ClipboardResult:
|
|
33
|
+
"""Result of a clipboard operation."""
|
|
34
|
+
|
|
35
|
+
success: bool
|
|
36
|
+
method: ClipboardMethod
|
|
37
|
+
message: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class ClipboardReadResult:
|
|
42
|
+
"""Result of a clipboard read operation."""
|
|
43
|
+
|
|
44
|
+
success: bool
|
|
45
|
+
method: ClipboardMethod
|
|
46
|
+
message: str
|
|
47
|
+
text: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_SUBPROCESS_COMMANDS: dict[ClipboardMethod, list[str]] = {
|
|
51
|
+
ClipboardMethod.PBCOPY: ["pbcopy"],
|
|
52
|
+
ClipboardMethod.XCLIP: ["xclip", "-selection", "clipboard"],
|
|
53
|
+
ClipboardMethod.XSEL: ["xsel", "--clipboard", "--input"],
|
|
54
|
+
ClipboardMethod.WL_COPY: ["wl-copy"],
|
|
55
|
+
ClipboardMethod.CLIP: ["clip"],
|
|
56
|
+
}
|
|
57
|
+
_SUBPROCESS_READ_COMMANDS: dict[ClipboardMethod, list[str]] = {
|
|
58
|
+
ClipboardMethod.PBCOPY: ["pbpaste"],
|
|
59
|
+
ClipboardMethod.XCLIP: ["xclip", "-selection", "clipboard", "-o"],
|
|
60
|
+
ClipboardMethod.XSEL: ["xsel", "--clipboard", "--output"],
|
|
61
|
+
ClipboardMethod.WL_COPY: ["wl-paste", "--no-newline"],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_ENV_CLIPBOARD_METHOD = "AIP_TUI_CLIPBOARD_METHOD"
|
|
65
|
+
_ENV_CLIPBOARD_FORCE = "AIP_TUI_CLIPBOARD_FORCE"
|
|
66
|
+
_ENV_METHOD_MAP = {
|
|
67
|
+
"osc52": ClipboardMethod.OSC52,
|
|
68
|
+
"pbcopy": ClipboardMethod.PBCOPY,
|
|
69
|
+
"xclip": ClipboardMethod.XCLIP,
|
|
70
|
+
"xsel": ClipboardMethod.XSEL,
|
|
71
|
+
"wl-copy": ClipboardMethod.WL_COPY,
|
|
72
|
+
"wl_copy": ClipboardMethod.WL_COPY,
|
|
73
|
+
"clip": ClipboardMethod.CLIP,
|
|
74
|
+
"none": ClipboardMethod.NONE,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_SUBPROCESS_TIMEOUT = 2.0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resolve_env_method() -> ClipboardMethod | None:
|
|
81
|
+
raw = os.getenv(_ENV_CLIPBOARD_METHOD)
|
|
82
|
+
if not raw:
|
|
83
|
+
return None
|
|
84
|
+
value = raw.strip().lower()
|
|
85
|
+
if value in ("auto", "default"):
|
|
86
|
+
return None
|
|
87
|
+
return _ENV_METHOD_MAP.get(value)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _is_env_force_enabled() -> bool:
|
|
91
|
+
raw = os.getenv(_ENV_CLIPBOARD_FORCE)
|
|
92
|
+
if not raw:
|
|
93
|
+
return False
|
|
94
|
+
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _resolve_windows_read_command() -> list[str] | None:
|
|
98
|
+
for shell in ("powershell", "pwsh"):
|
|
99
|
+
if shutil.which(shell):
|
|
100
|
+
return [shell, "-NoProfile", "-Command", "Get-Clipboard -Raw"]
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ClipboardAdapter:
|
|
105
|
+
"""Cross-platform clipboard access with OSC 52 fallback."""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
terminal: TerminalCapabilities | None = None,
|
|
111
|
+
method: ClipboardMethod | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Initialize the adapter."""
|
|
114
|
+
self._terminal = terminal
|
|
115
|
+
self._force_method = False
|
|
116
|
+
self._fallback_methods_cache: list[ClipboardMethod] | None = None
|
|
117
|
+
if method is not None:
|
|
118
|
+
self._method = method
|
|
119
|
+
else:
|
|
120
|
+
env_method = _resolve_env_method()
|
|
121
|
+
if env_method is not None:
|
|
122
|
+
self._method = env_method
|
|
123
|
+
self._force_method = _is_env_force_enabled()
|
|
124
|
+
else:
|
|
125
|
+
self._method = self._detect_method()
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def method(self) -> ClipboardMethod:
|
|
129
|
+
"""Return the detected clipboard backend."""
|
|
130
|
+
return self._method
|
|
131
|
+
|
|
132
|
+
def copy(self, text: str, *, writer: Callable[[str], Any] | None = None) -> ClipboardResult:
|
|
133
|
+
"""Copy text to clipboard using the best available method.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
text: Text to copy.
|
|
137
|
+
writer: Optional function to write OSC 52 sequence (e.g., self.app.console.write).
|
|
138
|
+
Defaults to sys.stdout.write if not provided.
|
|
139
|
+
"""
|
|
140
|
+
if self._method == ClipboardMethod.OSC52:
|
|
141
|
+
return self._copy_osc52(text, writer=writer)
|
|
142
|
+
|
|
143
|
+
command = _SUBPROCESS_COMMANDS.get(self._method)
|
|
144
|
+
if command is None:
|
|
145
|
+
if self._force_method:
|
|
146
|
+
return ClipboardResult(False, self._method, "Forced clipboard method unavailable.")
|
|
147
|
+
return self._copy_osc52(text, writer=writer)
|
|
148
|
+
|
|
149
|
+
result = self._copy_subprocess(command, text)
|
|
150
|
+
if not result.success:
|
|
151
|
+
if self._force_method or "timed out" in result.message:
|
|
152
|
+
return result
|
|
153
|
+
return self._copy_osc52(text, writer=writer)
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
def read(self) -> ClipboardReadResult:
|
|
158
|
+
"""Read text from the clipboard using the best available method."""
|
|
159
|
+
result = self._read_with_method(self._method)
|
|
160
|
+
if result.success or self._force_method:
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
if self._fallback_methods_cache is None:
|
|
164
|
+
self._fallback_methods_cache = self._fallback_read_methods()
|
|
165
|
+
|
|
166
|
+
for method in self._fallback_methods_cache:
|
|
167
|
+
if method is self._method:
|
|
168
|
+
continue
|
|
169
|
+
fallback = self._read_with_method(method)
|
|
170
|
+
if fallback.success:
|
|
171
|
+
return fallback
|
|
172
|
+
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
def _detect_method(self) -> ClipboardMethod:
|
|
176
|
+
system = platform.system()
|
|
177
|
+
method = ClipboardMethod.NONE
|
|
178
|
+
if system == "Darwin":
|
|
179
|
+
method = self._detect_darwin_method()
|
|
180
|
+
elif system == "Linux":
|
|
181
|
+
method = self._detect_linux_method()
|
|
182
|
+
elif system == "Windows":
|
|
183
|
+
method = self._detect_windows_method()
|
|
184
|
+
|
|
185
|
+
if method is not ClipboardMethod.NONE:
|
|
186
|
+
return method
|
|
187
|
+
|
|
188
|
+
if self._terminal.osc52 if self._terminal else detect_osc52_support():
|
|
189
|
+
return ClipboardMethod.OSC52
|
|
190
|
+
|
|
191
|
+
return ClipboardMethod.NONE
|
|
192
|
+
|
|
193
|
+
def _detect_darwin_method(self) -> ClipboardMethod:
|
|
194
|
+
return ClipboardMethod.PBCOPY if shutil.which("pbcopy") else ClipboardMethod.NONE
|
|
195
|
+
|
|
196
|
+
def _detect_linux_method(self) -> ClipboardMethod:
|
|
197
|
+
if not os.getenv("DISPLAY") and not os.getenv("WAYLAND_DISPLAY"):
|
|
198
|
+
return ClipboardMethod.NONE
|
|
199
|
+
|
|
200
|
+
# Order of preference: Wayland then X11 tools
|
|
201
|
+
for method in (ClipboardMethod.WL_COPY, ClipboardMethod.XCLIP, ClipboardMethod.XSEL):
|
|
202
|
+
cmd = _SUBPROCESS_COMMANDS.get(method)
|
|
203
|
+
if cmd and shutil.which(cmd[0]):
|
|
204
|
+
return method
|
|
205
|
+
return ClipboardMethod.NONE
|
|
206
|
+
|
|
207
|
+
def _detect_windows_method(self) -> ClipboardMethod:
|
|
208
|
+
return ClipboardMethod.CLIP if shutil.which("clip") else ClipboardMethod.NONE
|
|
209
|
+
|
|
210
|
+
def _copy_osc52(self, text: str, *, writer: Callable[[str], Any] | None = None) -> ClipboardResult:
|
|
211
|
+
encoded = base64.b64encode(text.encode("utf-8")).decode("ascii")
|
|
212
|
+
sequence = f"\x1b]52;c;{encoded}\x07"
|
|
213
|
+
try:
|
|
214
|
+
if writer:
|
|
215
|
+
writer(sequence)
|
|
216
|
+
else:
|
|
217
|
+
sys.stdout.write(sequence)
|
|
218
|
+
sys.stdout.flush()
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
return ClipboardResult(False, ClipboardMethod.OSC52, str(exc))
|
|
221
|
+
|
|
222
|
+
return ClipboardResult(True, ClipboardMethod.OSC52, "Copied to clipboard")
|
|
223
|
+
|
|
224
|
+
def _copy_subprocess(self, cmd: list[str], text: str) -> ClipboardResult:
|
|
225
|
+
try:
|
|
226
|
+
completed = subprocess.run(
|
|
227
|
+
cmd,
|
|
228
|
+
input=text.encode("utf-8"),
|
|
229
|
+
check=False,
|
|
230
|
+
timeout=_SUBPROCESS_TIMEOUT,
|
|
231
|
+
)
|
|
232
|
+
except subprocess.TimeoutExpired:
|
|
233
|
+
return ClipboardResult(False, self._method, f"Clipboard command timed out after {_SUBPROCESS_TIMEOUT}s")
|
|
234
|
+
except OSError as exc:
|
|
235
|
+
return ClipboardResult(False, self._method, str(exc))
|
|
236
|
+
|
|
237
|
+
if completed.returncode == 0:
|
|
238
|
+
return ClipboardResult(True, self._method, "Copied to clipboard")
|
|
239
|
+
|
|
240
|
+
return ClipboardResult(False, self._method, f"Command failed: {completed.returncode}")
|
|
241
|
+
|
|
242
|
+
def _read_with_method(self, method: ClipboardMethod) -> ClipboardReadResult:
|
|
243
|
+
if method is ClipboardMethod.OSC52:
|
|
244
|
+
# OSC 52 read requires an asynchronous terminal response (DSR) which is
|
|
245
|
+
# significantly more complex to implement than synchronous subprocess reads.
|
|
246
|
+
# Currently out of scope.
|
|
247
|
+
return ClipboardReadResult(False, method, "OSC 52 clipboard read is unsupported.", "")
|
|
248
|
+
if method is ClipboardMethod.NONE:
|
|
249
|
+
return ClipboardReadResult(False, method, "Clipboard backend unavailable.", "")
|
|
250
|
+
|
|
251
|
+
if method is ClipboardMethod.CLIP:
|
|
252
|
+
command = _resolve_windows_read_command()
|
|
253
|
+
if command is None:
|
|
254
|
+
return ClipboardReadResult(False, method, "PowerShell clipboard read unavailable.", "")
|
|
255
|
+
return self._read_subprocess(command, method)
|
|
256
|
+
|
|
257
|
+
command = _SUBPROCESS_READ_COMMANDS.get(method)
|
|
258
|
+
if command is None:
|
|
259
|
+
return ClipboardReadResult(False, method, "Clipboard read method unavailable.", "")
|
|
260
|
+
|
|
261
|
+
return self._read_subprocess(command, method)
|
|
262
|
+
|
|
263
|
+
def _read_subprocess(self, cmd: list[str], method: ClipboardMethod) -> ClipboardReadResult:
|
|
264
|
+
try:
|
|
265
|
+
completed = subprocess.run(
|
|
266
|
+
cmd,
|
|
267
|
+
capture_output=True,
|
|
268
|
+
text=True,
|
|
269
|
+
encoding="utf-8",
|
|
270
|
+
check=False,
|
|
271
|
+
timeout=_SUBPROCESS_TIMEOUT,
|
|
272
|
+
)
|
|
273
|
+
except subprocess.TimeoutExpired:
|
|
274
|
+
return ClipboardReadResult(False, method, f"Clipboard command timed out after {_SUBPROCESS_TIMEOUT}s", "")
|
|
275
|
+
except OSError as exc:
|
|
276
|
+
return ClipboardReadResult(False, method, str(exc), "")
|
|
277
|
+
|
|
278
|
+
if completed.returncode == 0:
|
|
279
|
+
return ClipboardReadResult(True, method, "Read from clipboard", completed.stdout)
|
|
280
|
+
|
|
281
|
+
return ClipboardReadResult(False, method, f"Command failed: {completed.returncode}", "")
|
|
282
|
+
|
|
283
|
+
def _fallback_read_methods(self) -> list[ClipboardMethod]:
|
|
284
|
+
system = platform.system()
|
|
285
|
+
if system == "Darwin":
|
|
286
|
+
return self._fallback_darwin()
|
|
287
|
+
if system == "Linux":
|
|
288
|
+
return self._fallback_linux()
|
|
289
|
+
if system == "Windows":
|
|
290
|
+
return self._fallback_windows()
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
def _fallback_darwin(self) -> list[ClipboardMethod]:
|
|
294
|
+
cmd = _SUBPROCESS_READ_COMMANDS.get(ClipboardMethod.PBCOPY)
|
|
295
|
+
if cmd and shutil.which(cmd[0]):
|
|
296
|
+
return [ClipboardMethod.PBCOPY]
|
|
297
|
+
return []
|
|
298
|
+
|
|
299
|
+
def _fallback_linux(self) -> list[ClipboardMethod]:
|
|
300
|
+
methods: list[ClipboardMethod] = []
|
|
301
|
+
for method in (ClipboardMethod.WL_COPY, ClipboardMethod.XCLIP, ClipboardMethod.XSEL):
|
|
302
|
+
cmd = _SUBPROCESS_READ_COMMANDS.get(method)
|
|
303
|
+
if not cmd:
|
|
304
|
+
continue
|
|
305
|
+
if method == ClipboardMethod.WL_COPY and not os.getenv("WAYLAND_DISPLAY"):
|
|
306
|
+
continue
|
|
307
|
+
if method in (ClipboardMethod.XCLIP, ClipboardMethod.XSEL) and not os.getenv("DISPLAY"):
|
|
308
|
+
continue
|
|
309
|
+
if shutil.which(cmd[0]):
|
|
310
|
+
methods.append(method)
|
|
311
|
+
return methods
|
|
312
|
+
|
|
313
|
+
def _fallback_windows(self) -> list[ClipboardMethod]:
|
|
314
|
+
if _resolve_windows_read_command():
|
|
315
|
+
return [ClipboardMethod.CLIP]
|
|
316
|
+
return []
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Shared context for all TUI components.
|
|
2
|
+
|
|
3
|
+
This module provides the TUIContext dataclass, which serves as the Python equivalent
|
|
4
|
+
of OpenCode's nested provider pattern. It provides a single container for all TUI
|
|
5
|
+
services and state that can be injected into components.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
from glaip_sdk.cli.account_store import get_account_store
|
|
17
|
+
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter
|
|
18
|
+
from glaip_sdk.cli.slash.tui.keybind_registry import KeybindRegistry
|
|
19
|
+
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities
|
|
20
|
+
from glaip_sdk.cli.slash.tui.theme import ThemeManager
|
|
21
|
+
from glaip_sdk.cli.slash.tui.toast import ToastBus
|
|
22
|
+
from glaip_sdk.cli.tui_settings import load_tui_settings
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class TUIContext:
|
|
27
|
+
"""Shared context for all TUI components (Python equivalent of OpenCode's providers).
|
|
28
|
+
|
|
29
|
+
This context provides access to all TUI services and state. Components that will
|
|
30
|
+
be implemented in later phases are typed as Optional and will be None initially.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
terminal: Terminal capability detection results.
|
|
34
|
+
keybinds: Central keybind registry (Phase 3).
|
|
35
|
+
theme: Theme manager for light/dark mode and color tokens (Phase 2).
|
|
36
|
+
toasts: Toast notification bus (Phase 4).
|
|
37
|
+
clipboard: Clipboard adapter with OSC 52 support (Phase 4).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
terminal: TerminalCapabilities
|
|
41
|
+
keybinds: KeybindRegistry | None = None
|
|
42
|
+
theme: ThemeManager | None = None
|
|
43
|
+
toasts: ToastBus | None = None
|
|
44
|
+
clipboard: ClipboardAdapter | None = None
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
async def create(cls, *, detect_osc11: bool = True) -> TUIContext:
|
|
48
|
+
"""Create a TUIContext instance with detected terminal capabilities.
|
|
49
|
+
|
|
50
|
+
This factory method detects terminal capabilities asynchronously and
|
|
51
|
+
returns a populated TUIContext instance with all services initialized
|
|
52
|
+
(keybinds, theme, toasts, clipboard).
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
detect_osc11: When False, skip OSC 11 background detection.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
TUIContext instance with all services initialized.
|
|
59
|
+
"""
|
|
60
|
+
terminal = await TerminalCapabilities.detect(detect_osc11=detect_osc11)
|
|
61
|
+
store = get_account_store()
|
|
62
|
+
settings = load_tui_settings(store=store)
|
|
63
|
+
|
|
64
|
+
env_theme = os.getenv("AIP_TUI_THEME")
|
|
65
|
+
env_theme = env_theme.strip() if env_theme else None
|
|
66
|
+
if env_theme and env_theme.lower() == "default":
|
|
67
|
+
env_theme = None
|
|
68
|
+
|
|
69
|
+
env_mouse = os.getenv("AIP_TUI_MOUSE_CAPTURE")
|
|
70
|
+
mouse_capture = settings.mouse_capture
|
|
71
|
+
if env_mouse is not None:
|
|
72
|
+
mouse_capture = env_mouse.lower() == "true"
|
|
73
|
+
|
|
74
|
+
terminal.mouse = mouse_capture
|
|
75
|
+
|
|
76
|
+
theme_name = env_theme or settings.theme_name
|
|
77
|
+
theme = ThemeManager(
|
|
78
|
+
terminal,
|
|
79
|
+
mode=settings.theme_mode,
|
|
80
|
+
theme=theme_name,
|
|
81
|
+
settings_store=store,
|
|
82
|
+
)
|
|
83
|
+
keybinds = KeybindRegistry()
|
|
84
|
+
toasts = ToastBus()
|
|
85
|
+
clipboard = ClipboardAdapter(terminal=terminal)
|
|
86
|
+
return cls(
|
|
87
|
+
terminal=terminal,
|
|
88
|
+
keybinds=keybinds,
|
|
89
|
+
theme=theme,
|
|
90
|
+
toasts=toasts,
|
|
91
|
+
clipboard=clipboard,
|
|
92
|
+
)
|