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
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -6,10 +6,13 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import asyncio
|
|
9
10
|
import importlib
|
|
10
11
|
import os
|
|
11
12
|
import shlex
|
|
12
13
|
import sys
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
13
16
|
from collections.abc import Callable, Iterable
|
|
14
17
|
from dataclasses import dataclass
|
|
15
18
|
from difflib import get_close_matches
|
|
@@ -18,6 +21,7 @@ from typing import Any
|
|
|
18
21
|
|
|
19
22
|
import click
|
|
20
23
|
from rich.console import Console, Group
|
|
24
|
+
from rich.live import Live
|
|
21
25
|
from rich.text import Text
|
|
22
26
|
|
|
23
27
|
from glaip_sdk.branding import (
|
|
@@ -32,16 +36,20 @@ from glaip_sdk.branding import (
|
|
|
32
36
|
SUCCESS_STYLE,
|
|
33
37
|
WARNING_STYLE,
|
|
34
38
|
AIPBranding,
|
|
39
|
+
LogoAnimator,
|
|
35
40
|
)
|
|
36
|
-
from glaip_sdk.cli.auth import resolve_api_url_from_context
|
|
37
41
|
from glaip_sdk.cli.account_store import get_account_store
|
|
42
|
+
from glaip_sdk.cli.auth import resolve_api_url_from_context
|
|
38
43
|
from glaip_sdk.cli.commands import transcripts as transcripts_cmd
|
|
39
44
|
from glaip_sdk.cli.commands.configure import _configure_interactive, load_config
|
|
40
45
|
from glaip_sdk.cli.commands.update import update_command
|
|
41
|
-
from glaip_sdk.cli.
|
|
46
|
+
from glaip_sdk.cli.core.context import get_client, restore_slash_session_context
|
|
47
|
+
from glaip_sdk.cli.core.output import format_size
|
|
48
|
+
from glaip_sdk.cli.core.prompting import _fuzzy_pick_for_resources
|
|
49
|
+
from glaip_sdk.cli.hints import command_hint, format_command_hint
|
|
42
50
|
from glaip_sdk.cli.slash.accounts_controller import AccountsController
|
|
43
|
-
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
44
51
|
from glaip_sdk.cli.slash.accounts_shared import env_credentials_present
|
|
52
|
+
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
45
53
|
from glaip_sdk.cli.slash.prompt import (
|
|
46
54
|
FormattedText,
|
|
47
55
|
PromptSession,
|
|
@@ -51,19 +59,13 @@ from glaip_sdk.cli.slash.prompt import (
|
|
|
51
59
|
to_formatted_text,
|
|
52
60
|
)
|
|
53
61
|
from glaip_sdk.cli.slash.remote_runs_controller import RemoteRunsController
|
|
62
|
+
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
54
63
|
from glaip_sdk.cli.transcript import (
|
|
55
64
|
export_cached_transcript,
|
|
56
65
|
load_history_snapshot,
|
|
57
66
|
)
|
|
58
67
|
from glaip_sdk.cli.transcript.viewer import ViewerContext, run_viewer_session
|
|
59
68
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
60
|
-
from glaip_sdk.cli.utils import (
|
|
61
|
-
_fuzzy_pick_for_resources,
|
|
62
|
-
command_hint,
|
|
63
|
-
format_size,
|
|
64
|
-
get_client,
|
|
65
|
-
restore_slash_session_context,
|
|
66
|
-
)
|
|
67
69
|
from glaip_sdk.rich_components import AIPGrid, AIPPanel, AIPTable
|
|
68
70
|
|
|
69
71
|
SlashHandler = Callable[["SlashSession", list[str], bool], bool]
|
|
@@ -145,6 +147,22 @@ def _quick_action_scope(action: dict[str, Any]) -> str:
|
|
|
145
147
|
return "global"
|
|
146
148
|
|
|
147
149
|
|
|
150
|
+
@dataclass
|
|
151
|
+
class AnimationState:
|
|
152
|
+
"""State for logo animation shared between threads.
|
|
153
|
+
|
|
154
|
+
Uses mutable lists for integer values to allow thread-safe updates
|
|
155
|
+
without requiring locks or atomic operations.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
pulse_step: list[int] # Current animation step position
|
|
159
|
+
pulse_direction: list[int] # Direction of pulse (1 or -1)
|
|
160
|
+
step_size: list[int] # Step size for animation
|
|
161
|
+
current_status: list[str] # Current status message
|
|
162
|
+
animation_running: threading.Event # Event signaling animation is running
|
|
163
|
+
stop_requested: threading.Event # Event signaling stop was requested
|
|
164
|
+
|
|
165
|
+
|
|
148
166
|
class SlashSession:
|
|
149
167
|
"""Interactive command palette controller."""
|
|
150
168
|
|
|
@@ -156,7 +174,11 @@ class SlashSession:
|
|
|
156
174
|
console: Optional console instance, creates default if None
|
|
157
175
|
"""
|
|
158
176
|
self.ctx = ctx
|
|
159
|
-
self.
|
|
177
|
+
self._interactive = bool(sys.stdin.isatty() and sys.stdout.isatty())
|
|
178
|
+
if console is None:
|
|
179
|
+
self.console = AIPBranding._make_console(force_terminal=self._interactive, soft_wrap=False)
|
|
180
|
+
else:
|
|
181
|
+
self.console = console
|
|
160
182
|
self._commands: dict[str, SlashCommand] = {}
|
|
161
183
|
self._unique_commands: dict[str, SlashCommand] = {}
|
|
162
184
|
self._contextual_commands: dict[str, str] = {}
|
|
@@ -165,7 +187,6 @@ class SlashSession:
|
|
|
165
187
|
self.recent_agents: list[dict[str, str]] = []
|
|
166
188
|
self.last_run_input: str | None = None
|
|
167
189
|
self._should_exit = False
|
|
168
|
-
self._interactive = bool(sys.stdin.isatty() and sys.stdout.isatty())
|
|
169
190
|
self._config_cache: dict[str, Any] | None = None
|
|
170
191
|
self._welcome_rendered = False
|
|
171
192
|
self._active_renderer: Any | None = None
|
|
@@ -189,6 +210,16 @@ class SlashSession:
|
|
|
189
210
|
self._update_notifier = maybe_notify_update
|
|
190
211
|
self._home_hint_shown = False
|
|
191
212
|
self._agent_transcript_ready: dict[str, str] = {}
|
|
213
|
+
self.tui_ctx: TUIContext | None = None
|
|
214
|
+
|
|
215
|
+
# Animation configuration constants
|
|
216
|
+
ANIMATION_FPS = 20
|
|
217
|
+
ANIMATION_FRAME_DURATION = 1.0 / ANIMATION_FPS # 0.05 seconds
|
|
218
|
+
ANIMATION_STARTUP_DELAY = 0.1 # Delay to ensure animation starts
|
|
219
|
+
|
|
220
|
+
# Startup UI constants
|
|
221
|
+
INITIALIZING_STATUS = "Initializing..."
|
|
222
|
+
CLI_HEADING_MARKUP = "[bold]>_ GDP Labs AI Agents Package (AIP CLI)[/bold]"
|
|
192
223
|
|
|
193
224
|
# ------------------------------------------------------------------
|
|
194
225
|
# Session orchestration
|
|
@@ -229,11 +260,19 @@ class SlashSession:
|
|
|
229
260
|
self._run_non_interactive(initial_commands)
|
|
230
261
|
return
|
|
231
262
|
|
|
232
|
-
|
|
263
|
+
self._maybe_show_update_prompt()
|
|
264
|
+
# Use animated logo during initialization if supported
|
|
265
|
+
animator = LogoAnimator(console=self.console)
|
|
266
|
+
if animator.should_animate() and self._interactive:
|
|
267
|
+
config_available = self._run_with_animated_logo(animator)
|
|
268
|
+
else:
|
|
269
|
+
# Fallback to static logo for non-TTY or NO_COLOR
|
|
270
|
+
config_available = self._run_with_static_logo(animator)
|
|
271
|
+
|
|
272
|
+
if not config_available:
|
|
233
273
|
return
|
|
234
274
|
|
|
235
|
-
self.
|
|
236
|
-
self._render_header(initial=not self._welcome_rendered)
|
|
275
|
+
self._render_header(initial=not self._welcome_rendered, show_branding=False)
|
|
237
276
|
if not self._default_actions_shown:
|
|
238
277
|
self._show_default_quick_actions()
|
|
239
278
|
self._run_interactive_loop()
|
|
@@ -241,6 +280,284 @@ class SlashSession:
|
|
|
241
280
|
if ctx_obj is not None:
|
|
242
281
|
restore_slash_session_context(ctx_obj, previous_session)
|
|
243
282
|
|
|
283
|
+
def _initialize_tui_context(self) -> None:
|
|
284
|
+
"""Initialize TUI context with error handling.
|
|
285
|
+
|
|
286
|
+
Sets self.tui_ctx to None if initialization fails.
|
|
287
|
+
"""
|
|
288
|
+
try:
|
|
289
|
+
self.tui_ctx = asyncio.run(TUIContext.create(detect_osc11=False))
|
|
290
|
+
except RuntimeError:
|
|
291
|
+
try:
|
|
292
|
+
loop = asyncio.get_event_loop()
|
|
293
|
+
except RuntimeError:
|
|
294
|
+
self.tui_ctx = None
|
|
295
|
+
else:
|
|
296
|
+
if loop.is_running():
|
|
297
|
+
self.tui_ctx = None
|
|
298
|
+
else:
|
|
299
|
+
self.tui_ctx = loop.run_until_complete(TUIContext.create(detect_osc11=False))
|
|
300
|
+
except Exception:
|
|
301
|
+
self.tui_ctx = None
|
|
302
|
+
|
|
303
|
+
def _run_initialization_tasks(
|
|
304
|
+
self,
|
|
305
|
+
current_status: list[str],
|
|
306
|
+
animation_running: threading.Event,
|
|
307
|
+
status_callback: Callable[[str], None] | None = None,
|
|
308
|
+
) -> bool:
|
|
309
|
+
"""Run initialization tasks with status updates.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
current_status: Mutable list with current status message.
|
|
313
|
+
animation_running: Event to signal animation state.
|
|
314
|
+
status_callback: Optional callback to invoke when status changes.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
True if configuration is available, False otherwise.
|
|
318
|
+
"""
|
|
319
|
+
# Task 1: TUI Context.
|
|
320
|
+
current_status[0] = "Detecting terminal..."
|
|
321
|
+
if status_callback:
|
|
322
|
+
status_callback(current_status[0])
|
|
323
|
+
self._initialize_tui_context()
|
|
324
|
+
|
|
325
|
+
# Task 2: Configuration.
|
|
326
|
+
current_status[0] = "Connecting to API..."
|
|
327
|
+
if status_callback:
|
|
328
|
+
status_callback(current_status[0])
|
|
329
|
+
if not self._ensure_configuration():
|
|
330
|
+
animation_running.clear()
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
def _update_pulse_step(
|
|
336
|
+
self,
|
|
337
|
+
state: AnimationState,
|
|
338
|
+
animator: LogoAnimator,
|
|
339
|
+
) -> bool:
|
|
340
|
+
"""Update pulse step and direction.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
state: Animation state container.
|
|
344
|
+
animator: LogoAnimator instance for animation.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
True if animation should continue, False if should stop.
|
|
348
|
+
"""
|
|
349
|
+
state.pulse_step[0] += state.pulse_direction[0] * state.step_size[0]
|
|
350
|
+
if state.pulse_step[0] >= animator.max_width + 5:
|
|
351
|
+
state.pulse_step[0] = animator.max_width + 5
|
|
352
|
+
state.pulse_direction[0] = -1
|
|
353
|
+
return not state.stop_requested.is_set()
|
|
354
|
+
if state.pulse_step[0] <= -5:
|
|
355
|
+
state.pulse_step[0] = -5
|
|
356
|
+
state.pulse_direction[0] = 1
|
|
357
|
+
return not state.stop_requested.is_set()
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
def _create_animation_updater(
|
|
361
|
+
self,
|
|
362
|
+
animator: LogoAnimator,
|
|
363
|
+
state: AnimationState,
|
|
364
|
+
heading: Text,
|
|
365
|
+
) -> Callable[[Live], None]:
|
|
366
|
+
"""Create animation update function for background thread.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
animator: LogoAnimator instance for animation.
|
|
370
|
+
state: Animation state container.
|
|
371
|
+
heading: Text heading for frames.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Function to update animation in background thread.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
def build_frame(step: int, status_text: str) -> Group:
|
|
378
|
+
return Group(heading, Text(""), animator.generate_frame(step, status_text))
|
|
379
|
+
|
|
380
|
+
def update_animation(live: Live) -> None:
|
|
381
|
+
"""Update animation in background thread."""
|
|
382
|
+
while state.animation_running.is_set():
|
|
383
|
+
# Calculate next step
|
|
384
|
+
if not self._update_pulse_step(state, animator):
|
|
385
|
+
break
|
|
386
|
+
|
|
387
|
+
# Update frame with current status
|
|
388
|
+
try:
|
|
389
|
+
live.update(build_frame(state.pulse_step[0], state.current_status[0]))
|
|
390
|
+
except Exception:
|
|
391
|
+
# Animation may be stopped, ignore errors
|
|
392
|
+
break
|
|
393
|
+
time.sleep(self.ANIMATION_FRAME_DURATION)
|
|
394
|
+
state.animation_running.clear()
|
|
395
|
+
|
|
396
|
+
return update_animation
|
|
397
|
+
|
|
398
|
+
def _stop_animation_thread(
|
|
399
|
+
self,
|
|
400
|
+
animation_thread: threading.Thread,
|
|
401
|
+
state: AnimationState,
|
|
402
|
+
) -> None:
|
|
403
|
+
"""Stop animation thread gracefully.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
animation_thread: Thread running animation.
|
|
407
|
+
state: Animation state container.
|
|
408
|
+
"""
|
|
409
|
+
state.stop_requested.set()
|
|
410
|
+
state.step_size[0] = 3
|
|
411
|
+
animation_thread.join(timeout=1.5)
|
|
412
|
+
if animation_thread.is_alive():
|
|
413
|
+
state.animation_running.clear()
|
|
414
|
+
animation_thread.join(timeout=0.2)
|
|
415
|
+
|
|
416
|
+
def _run_animated_initialization(
|
|
417
|
+
self,
|
|
418
|
+
live: Live,
|
|
419
|
+
animator: LogoAnimator,
|
|
420
|
+
state: AnimationState,
|
|
421
|
+
heading: Text,
|
|
422
|
+
banner: Text,
|
|
423
|
+
) -> bool:
|
|
424
|
+
"""Run initialization tasks with animated logo.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
live: Live context for animation updates.
|
|
428
|
+
animator: LogoAnimator instance for animation.
|
|
429
|
+
state: Animation state container.
|
|
430
|
+
heading: Text heading for frames.
|
|
431
|
+
banner: Text banner for final display.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
True if configuration is available, False otherwise.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
def build_banner() -> Group:
|
|
438
|
+
return Group(heading, Text(""), banner)
|
|
439
|
+
|
|
440
|
+
update_animation = self._create_animation_updater(
|
|
441
|
+
animator,
|
|
442
|
+
state,
|
|
443
|
+
heading,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Start animation thread.
|
|
447
|
+
animation_thread = threading.Thread(target=update_animation, args=(live,), daemon=True)
|
|
448
|
+
animation_thread.start()
|
|
449
|
+
|
|
450
|
+
# Small delay to ensure animation starts.
|
|
451
|
+
time.sleep(self.ANIMATION_STARTUP_DELAY)
|
|
452
|
+
|
|
453
|
+
def update_status(status: str) -> None:
|
|
454
|
+
state.current_status[0] = status
|
|
455
|
+
|
|
456
|
+
# Run initialization tasks.
|
|
457
|
+
if not self._run_initialization_tasks(
|
|
458
|
+
state.current_status,
|
|
459
|
+
state.animation_running,
|
|
460
|
+
status_callback=update_status,
|
|
461
|
+
):
|
|
462
|
+
return False
|
|
463
|
+
|
|
464
|
+
# Stop animation and show final banner.
|
|
465
|
+
self._stop_animation_thread(animation_thread, state)
|
|
466
|
+
live.update(build_banner())
|
|
467
|
+
return True
|
|
468
|
+
|
|
469
|
+
def _run_with_animated_logo(self, animator: LogoAnimator) -> bool:
|
|
470
|
+
"""Run initialization with animated logo.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
animator: LogoAnimator instance for animation.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
True if configuration is available, False otherwise.
|
|
477
|
+
"""
|
|
478
|
+
state = AnimationState(
|
|
479
|
+
pulse_step=[0], # Use list for mutable shared state.
|
|
480
|
+
pulse_direction=[1], # Use list for mutable shared state.
|
|
481
|
+
step_size=[1],
|
|
482
|
+
current_status=[self.INITIALIZING_STATUS],
|
|
483
|
+
animation_running=threading.Event(),
|
|
484
|
+
stop_requested=threading.Event(),
|
|
485
|
+
)
|
|
486
|
+
state.animation_running.set()
|
|
487
|
+
heading = Text.from_markup(self.CLI_HEADING_MARKUP)
|
|
488
|
+
banner = Text.from_markup(self._branding.get_welcome_banner())
|
|
489
|
+
|
|
490
|
+
def build_frame(step: int, status_text: str) -> Group:
|
|
491
|
+
return Group(heading, Text(""), animator.generate_frame(step, status_text))
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
with Live(
|
|
495
|
+
build_frame(0, state.current_status[0]),
|
|
496
|
+
console=self.console,
|
|
497
|
+
refresh_per_second=self.ANIMATION_FPS,
|
|
498
|
+
transient=False,
|
|
499
|
+
) as live:
|
|
500
|
+
return self._run_animated_initialization(
|
|
501
|
+
live,
|
|
502
|
+
animator,
|
|
503
|
+
state,
|
|
504
|
+
heading,
|
|
505
|
+
banner,
|
|
506
|
+
)
|
|
507
|
+
except KeyboardInterrupt:
|
|
508
|
+
# Graceful exit on Ctrl+C
|
|
509
|
+
state.animation_running.clear()
|
|
510
|
+
# Align with static path: show heading and cancellation message
|
|
511
|
+
heading = Text.from_markup(self.CLI_HEADING_MARKUP)
|
|
512
|
+
self.console.print(Group(heading, Text(""), animator.static_frame("Initialization cancelled.")))
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
def _run_with_static_logo(self, animator: LogoAnimator) -> bool:
|
|
516
|
+
"""Run initialization with static logo (non-TTY or NO_COLOR).
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
animator: LogoAnimator instance for static display.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
True if configuration is available, False otherwise.
|
|
523
|
+
"""
|
|
524
|
+
heading = Text.from_markup(self.CLI_HEADING_MARKUP)
|
|
525
|
+
banner = Text.from_markup(self._branding.get_welcome_banner())
|
|
526
|
+
|
|
527
|
+
def build_frame(status_text: str) -> Group:
|
|
528
|
+
return Group(heading, Text(""), animator.static_frame(status_text))
|
|
529
|
+
|
|
530
|
+
def build_banner() -> Group:
|
|
531
|
+
return Group(heading, Text(""), banner)
|
|
532
|
+
|
|
533
|
+
try:
|
|
534
|
+
with Live(
|
|
535
|
+
build_frame(self.INITIALIZING_STATUS),
|
|
536
|
+
console=self.console,
|
|
537
|
+
refresh_per_second=4,
|
|
538
|
+
transient=False,
|
|
539
|
+
) as live:
|
|
540
|
+
# Run initialization tasks with status updates, reusing shared logic.
|
|
541
|
+
current_status = [self.INITIALIZING_STATUS]
|
|
542
|
+
animation_running = threading.Event()
|
|
543
|
+
animation_running.set()
|
|
544
|
+
|
|
545
|
+
# Update Live display when status changes via callback.
|
|
546
|
+
def update_display(status: str) -> None:
|
|
547
|
+
"""Update Live display with current status."""
|
|
548
|
+
live.update(build_frame(status))
|
|
549
|
+
|
|
550
|
+
if not self._run_initialization_tasks(
|
|
551
|
+
current_status, animation_running, status_callback=update_display
|
|
552
|
+
):
|
|
553
|
+
return False
|
|
554
|
+
|
|
555
|
+
live.update(build_banner())
|
|
556
|
+
return True
|
|
557
|
+
except KeyboardInterrupt:
|
|
558
|
+
self.console.print(Group(heading, Text(""), animator.static_frame("Initialization cancelled.")))
|
|
559
|
+
return False
|
|
560
|
+
|
|
244
561
|
def _run_interactive_loop(self) -> None:
|
|
245
562
|
"""Run the main interactive command loop."""
|
|
246
563
|
while not self._should_exit:
|
|
@@ -900,7 +1217,7 @@ class SlashSession:
|
|
|
900
1217
|
self._register(
|
|
901
1218
|
SlashCommand(
|
|
902
1219
|
name="accounts",
|
|
903
|
-
help="✨ NEW · Browse and switch stored accounts
|
|
1220
|
+
help="✨ NEW · Browse and switch stored accounts.",
|
|
904
1221
|
handler=SlashSession._cmd_accounts,
|
|
905
1222
|
)
|
|
906
1223
|
)
|
|
@@ -1273,6 +1590,7 @@ class SlashSession:
|
|
|
1273
1590
|
*,
|
|
1274
1591
|
focus_agent: bool = False,
|
|
1275
1592
|
initial: bool = False,
|
|
1593
|
+
show_branding: bool = True,
|
|
1276
1594
|
) -> None:
|
|
1277
1595
|
"""Render the session header with branding and status.
|
|
1278
1596
|
|
|
@@ -1280,14 +1598,16 @@ class SlashSession:
|
|
|
1280
1598
|
active_agent: Optional active agent to display.
|
|
1281
1599
|
focus_agent: Whether to focus on agent display.
|
|
1282
1600
|
initial: Whether this is the initial render.
|
|
1601
|
+
show_branding: Whether to render the branding banner.
|
|
1283
1602
|
"""
|
|
1284
1603
|
if focus_agent and active_agent is not None:
|
|
1285
1604
|
self._render_focused_agent_header(active_agent)
|
|
1286
1605
|
return
|
|
1287
1606
|
|
|
1288
1607
|
full_header = initial or not self._welcome_rendered
|
|
1289
|
-
if full_header:
|
|
1608
|
+
if full_header and show_branding:
|
|
1290
1609
|
self._render_branding_banner()
|
|
1610
|
+
if full_header:
|
|
1291
1611
|
self.console.rule(style=PRIMARY)
|
|
1292
1612
|
self._render_main_header(active_agent, full=full_header)
|
|
1293
1613
|
if full_header:
|
|
@@ -1297,14 +1617,17 @@ class SlashSession:
|
|
|
1297
1617
|
def _render_branding_banner(self) -> None:
|
|
1298
1618
|
"""Render the GL AIP branding banner."""
|
|
1299
1619
|
banner = self._branding.get_welcome_banner()
|
|
1300
|
-
heading =
|
|
1620
|
+
heading = self.CLI_HEADING_MARKUP
|
|
1301
1621
|
self.console.print(heading)
|
|
1302
1622
|
self.console.print()
|
|
1303
1623
|
self.console.print(banner)
|
|
1304
1624
|
|
|
1305
|
-
def _maybe_show_update_prompt(self) -> None:
|
|
1625
|
+
def _maybe_show_update_prompt(self, *, defer: bool = False) -> None:
|
|
1306
1626
|
"""Display update prompt once per session when applicable."""
|
|
1307
|
-
if self._update_prompt_shown:
|
|
1627
|
+
if self._update_prompt_shown or (defer and not self._update_prompt_shown):
|
|
1628
|
+
if defer:
|
|
1629
|
+
# Just mark as ready to show, but don't show yet
|
|
1630
|
+
return
|
|
1308
1631
|
return
|
|
1309
1632
|
|
|
1310
1633
|
self._update_notifier(
|
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
"""Textual UI helpers for slash commands."""
|
|
2
2
|
|
|
3
|
+
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter, ClipboardReadResult, ClipboardResult
|
|
4
|
+
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
5
|
+
from glaip_sdk.cli.slash.tui.indicators import PulseIndicator
|
|
6
|
+
from glaip_sdk.cli.slash.tui.keybind_registry import (
|
|
7
|
+
Keybind,
|
|
8
|
+
KeybindRegistry,
|
|
9
|
+
format_key_sequence,
|
|
10
|
+
parse_key_sequence,
|
|
11
|
+
)
|
|
3
12
|
from glaip_sdk.cli.slash.tui.remote_runs_app import (
|
|
4
13
|
RemoteRunsTextualApp,
|
|
5
14
|
RemoteRunsTUICallbacks,
|
|
6
15
|
run_remote_runs_textual,
|
|
7
16
|
)
|
|
17
|
+
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities, detect_terminal_background
|
|
18
|
+
from glaip_sdk.cli.slash.tui.toast import ToastBus, ToastVariant
|
|
8
19
|
|
|
9
|
-
__all__ = [
|
|
20
|
+
__all__ = [
|
|
21
|
+
"TUIContext",
|
|
22
|
+
"ToastBus",
|
|
23
|
+
"ToastVariant",
|
|
24
|
+
"TerminalCapabilities",
|
|
25
|
+
"detect_terminal_background",
|
|
26
|
+
"RemoteRunsTextualApp",
|
|
27
|
+
"RemoteRunsTUICallbacks",
|
|
28
|
+
"run_remote_runs_textual",
|
|
29
|
+
"KeybindRegistry",
|
|
30
|
+
"Keybind",
|
|
31
|
+
"parse_key_sequence",
|
|
32
|
+
"format_key_sequence",
|
|
33
|
+
"ClipboardAdapter",
|
|
34
|
+
"ClipboardReadResult",
|
|
35
|
+
"ClipboardResult",
|
|
36
|
+
"PulseIndicator",
|
|
37
|
+
]
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
* Keep layout compact: filter sits tight above the table; header shows active account.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
Screen {
|
|
7
|
+
layers: base toasts;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
#toast-container {
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: auto;
|
|
13
|
+
dock: top;
|
|
14
|
+
align: right top;
|
|
15
|
+
layer: toasts;
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
#header-info {
|
|
7
19
|
padding: 0 1 0 1;
|
|
8
20
|
margin: 0;
|
|
@@ -11,7 +23,7 @@
|
|
|
11
23
|
|
|
12
24
|
#env-lock {
|
|
13
25
|
padding: 0 1 0 1;
|
|
14
|
-
color:
|
|
26
|
+
color: $warning;
|
|
15
27
|
height: 1;
|
|
16
28
|
}
|
|
17
29
|
|
|
@@ -41,10 +53,23 @@
|
|
|
41
53
|
margin-left: 1;
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
Button:hover {
|
|
57
|
+
background: $surface-lighten-1;
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
#accounts-table {
|
|
45
61
|
padding: 0 1 0 1;
|
|
46
62
|
margin: 0 0 0 0;
|
|
47
63
|
height: 1fr;
|
|
64
|
+
border: tall $primary;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#accounts-table > .datatable--row:hover {
|
|
68
|
+
background: $surface-lighten-1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sidebar-block:hover {
|
|
72
|
+
background: $surface-lighten-1;
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
#status-bar {
|
|
@@ -54,15 +79,15 @@
|
|
|
54
79
|
|
|
55
80
|
#accounts-loading {
|
|
56
81
|
width: 8;
|
|
57
|
-
display: none;
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
#status {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
color:
|
|
85
|
+
height: 3;
|
|
86
|
+
padding: 0 1;
|
|
87
|
+
color: $secondary;
|
|
64
88
|
}
|
|
65
89
|
|
|
90
|
+
|
|
66
91
|
.form-label {
|
|
67
92
|
padding: 0 1 0 1;
|
|
68
93
|
}
|
|
@@ -73,7 +98,7 @@
|
|
|
73
98
|
|
|
74
99
|
#form-status, #confirm-status {
|
|
75
100
|
padding: 0 1;
|
|
76
|
-
color:
|
|
101
|
+
color: $warning;
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
#form-test {
|
|
@@ -84,3 +109,69 @@
|
|
|
84
109
|
#form-actions {
|
|
85
110
|
margin: 0 1 0 1;
|
|
86
111
|
}
|
|
112
|
+
|
|
113
|
+
/* Harlequin Layout Styling */
|
|
114
|
+
|
|
115
|
+
#left-pane-title, #right-pane-title {
|
|
116
|
+
padding: 1;
|
|
117
|
+
text-style: bold;
|
|
118
|
+
border-bottom: solid $primary;
|
|
119
|
+
height: 3;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#harlequin-filter {
|
|
123
|
+
padding: 0 1;
|
|
124
|
+
margin: 1;
|
|
125
|
+
height: 3;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#harlequin-accounts-list {
|
|
129
|
+
padding: 0 1;
|
|
130
|
+
margin: 1;
|
|
131
|
+
height: 1fr;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#left-content {
|
|
135
|
+
height: 100%;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#right-content {
|
|
139
|
+
padding: 1;
|
|
140
|
+
height: 100%;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.detail-label {
|
|
144
|
+
padding: 0 1 0 0;
|
|
145
|
+
text-style: bold;
|
|
146
|
+
width: 12;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#detail-fields {
|
|
150
|
+
padding: 1 0;
|
|
151
|
+
height: auto;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#harlequin-detail-url,
|
|
155
|
+
#harlequin-detail-key,
|
|
156
|
+
#harlequin-detail-status {
|
|
157
|
+
padding: 0 1;
|
|
158
|
+
margin-bottom: 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#harlequin-detail-actions {
|
|
162
|
+
padding: 1 0;
|
|
163
|
+
margin-top: 1;
|
|
164
|
+
height: auto;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#harlequin-loading {
|
|
168
|
+
width: auto;
|
|
169
|
+
height: 3;
|
|
170
|
+
padding: 0 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#harlequin-status {
|
|
174
|
+
padding: 1;
|
|
175
|
+
margin-top: 1;
|
|
176
|
+
height: auto;
|
|
177
|
+
}
|