glaip-sdk 0.7.9__py3-none-any.whl → 0.7.11__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 +61 -10
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/slash/remote_runs_controller.py +2 -0
- glaip_sdk/cli/slash/session.py +331 -30
- glaip_sdk/cli/slash/tui/accounts.tcss +72 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +827 -101
- glaip_sdk/cli/slash/tui/clipboard.py +56 -8
- glaip_sdk/cli/slash/tui/context.py +5 -2
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +160 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
- glaip_sdk/cli/slash/tui/terminal.py +8 -3
- glaip_sdk/cli/slash/tui/toast.py +270 -19
- glaip_sdk/client/run_rendering.py +76 -29
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/runner/langgraph.py +1 -0
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/METADATA +3 -1
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/RECORD +24 -19
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/top_level.txt +0 -0
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -11,6 +11,8 @@ import importlib
|
|
|
11
11
|
import os
|
|
12
12
|
import shlex
|
|
13
13
|
import sys
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
14
16
|
from collections.abc import Callable, Iterable
|
|
15
17
|
from dataclasses import dataclass
|
|
16
18
|
from difflib import get_close_matches
|
|
@@ -19,6 +21,7 @@ from typing import Any
|
|
|
19
21
|
|
|
20
22
|
import click
|
|
21
23
|
from rich.console import Console, Group
|
|
24
|
+
from rich.live import Live
|
|
22
25
|
from rich.text import Text
|
|
23
26
|
|
|
24
27
|
from glaip_sdk.branding import (
|
|
@@ -33,16 +36,20 @@ from glaip_sdk.branding import (
|
|
|
33
36
|
SUCCESS_STYLE,
|
|
34
37
|
WARNING_STYLE,
|
|
35
38
|
AIPBranding,
|
|
39
|
+
LogoAnimator,
|
|
36
40
|
)
|
|
37
|
-
from glaip_sdk.cli.auth import resolve_api_url_from_context
|
|
38
41
|
from glaip_sdk.cli.account_store import get_account_store
|
|
42
|
+
from glaip_sdk.cli.auth import resolve_api_url_from_context
|
|
39
43
|
from glaip_sdk.cli.commands import transcripts as transcripts_cmd
|
|
40
44
|
from glaip_sdk.cli.commands.configure import _configure_interactive, load_config
|
|
41
45
|
from glaip_sdk.cli.commands.update import update_command
|
|
42
|
-
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
|
|
43
50
|
from glaip_sdk.cli.slash.accounts_controller import AccountsController
|
|
44
|
-
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
45
51
|
from glaip_sdk.cli.slash.accounts_shared import env_credentials_present
|
|
52
|
+
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
46
53
|
from glaip_sdk.cli.slash.prompt import (
|
|
47
54
|
FormattedText,
|
|
48
55
|
PromptSession,
|
|
@@ -59,10 +66,6 @@ from glaip_sdk.cli.transcript import (
|
|
|
59
66
|
)
|
|
60
67
|
from glaip_sdk.cli.transcript.viewer import ViewerContext, run_viewer_session
|
|
61
68
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
62
|
-
from glaip_sdk.cli.core.context import get_client, restore_slash_session_context
|
|
63
|
-
from glaip_sdk.cli.core.output import format_size
|
|
64
|
-
from glaip_sdk.cli.core.prompting import _fuzzy_pick_for_resources
|
|
65
|
-
from glaip_sdk.cli.hints import command_hint
|
|
66
69
|
from glaip_sdk.rich_components import AIPGrid, AIPPanel, AIPTable
|
|
67
70
|
|
|
68
71
|
SlashHandler = Callable[["SlashSession", list[str], bool], bool]
|
|
@@ -144,6 +147,22 @@ def _quick_action_scope(action: dict[str, Any]) -> str:
|
|
|
144
147
|
return "global"
|
|
145
148
|
|
|
146
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
|
+
|
|
147
166
|
class SlashSession:
|
|
148
167
|
"""Interactive command palette controller."""
|
|
149
168
|
|
|
@@ -155,7 +174,11 @@ class SlashSession:
|
|
|
155
174
|
console: Optional console instance, creates default if None
|
|
156
175
|
"""
|
|
157
176
|
self.ctx = ctx
|
|
158
|
-
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
|
|
159
182
|
self._commands: dict[str, SlashCommand] = {}
|
|
160
183
|
self._unique_commands: dict[str, SlashCommand] = {}
|
|
161
184
|
self._contextual_commands: dict[str, str] = {}
|
|
@@ -164,7 +187,6 @@ class SlashSession:
|
|
|
164
187
|
self.recent_agents: list[dict[str, str]] = []
|
|
165
188
|
self.last_run_input: str | None = None
|
|
166
189
|
self._should_exit = False
|
|
167
|
-
self._interactive = bool(sys.stdin.isatty() and sys.stdout.isatty())
|
|
168
190
|
self._config_cache: dict[str, Any] | None = None
|
|
169
191
|
self._welcome_rendered = False
|
|
170
192
|
self._active_renderer: Any | None = None
|
|
@@ -190,6 +212,15 @@ class SlashSession:
|
|
|
190
212
|
self._agent_transcript_ready: dict[str, str] = {}
|
|
191
213
|
self.tui_ctx: TUIContext | None = None
|
|
192
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]"
|
|
223
|
+
|
|
193
224
|
# ------------------------------------------------------------------
|
|
194
225
|
# Session orchestration
|
|
195
226
|
# ------------------------------------------------------------------
|
|
@@ -218,22 +249,6 @@ class SlashSession:
|
|
|
218
249
|
|
|
219
250
|
def run(self, initial_commands: Iterable[str] | None = None) -> None:
|
|
220
251
|
"""Start the command palette session loop."""
|
|
221
|
-
# Initialize TUI context asynchronously
|
|
222
|
-
try:
|
|
223
|
-
self.tui_ctx = asyncio.run(TUIContext.create())
|
|
224
|
-
except RuntimeError:
|
|
225
|
-
try:
|
|
226
|
-
loop = asyncio.get_event_loop()
|
|
227
|
-
except RuntimeError:
|
|
228
|
-
self.tui_ctx = None
|
|
229
|
-
else:
|
|
230
|
-
if loop.is_running():
|
|
231
|
-
self.tui_ctx = None
|
|
232
|
-
else:
|
|
233
|
-
self.tui_ctx = loop.run_until_complete(TUIContext.create())
|
|
234
|
-
except Exception:
|
|
235
|
-
self.tui_ctx = None
|
|
236
|
-
|
|
237
252
|
ctx_obj = self.ctx.obj if isinstance(self.ctx.obj, dict) else None
|
|
238
253
|
previous_session = None
|
|
239
254
|
if ctx_obj is not None:
|
|
@@ -245,11 +260,18 @@ class SlashSession:
|
|
|
245
260
|
self._run_non_interactive(initial_commands)
|
|
246
261
|
return
|
|
247
262
|
|
|
248
|
-
if
|
|
263
|
+
# Use animated logo during initialization if supported
|
|
264
|
+
animator = LogoAnimator(console=self.console)
|
|
265
|
+
if animator.should_animate() and self._interactive:
|
|
266
|
+
config_available = self._run_with_animated_logo(animator)
|
|
267
|
+
else:
|
|
268
|
+
# Fallback to static logo for non-TTY or NO_COLOR
|
|
269
|
+
config_available = self._run_with_static_logo(animator)
|
|
270
|
+
|
|
271
|
+
if not config_available:
|
|
249
272
|
return
|
|
250
273
|
|
|
251
|
-
self.
|
|
252
|
-
self._render_header(initial=not self._welcome_rendered)
|
|
274
|
+
self._render_header(initial=not self._welcome_rendered, show_branding=False)
|
|
253
275
|
if not self._default_actions_shown:
|
|
254
276
|
self._show_default_quick_actions()
|
|
255
277
|
self._run_interactive_loop()
|
|
@@ -257,6 +279,282 @@ class SlashSession:
|
|
|
257
279
|
if ctx_obj is not None:
|
|
258
280
|
restore_slash_session_context(ctx_obj, previous_session)
|
|
259
281
|
|
|
282
|
+
def _initialize_tui_context(self) -> None:
|
|
283
|
+
"""Initialize TUI context with error handling.
|
|
284
|
+
|
|
285
|
+
Sets self.tui_ctx to None if initialization fails.
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
self.tui_ctx = asyncio.run(TUIContext.create(detect_osc11=False))
|
|
289
|
+
except RuntimeError:
|
|
290
|
+
try:
|
|
291
|
+
loop = asyncio.get_event_loop()
|
|
292
|
+
except RuntimeError:
|
|
293
|
+
self.tui_ctx = None
|
|
294
|
+
else:
|
|
295
|
+
if loop.is_running():
|
|
296
|
+
self.tui_ctx = None
|
|
297
|
+
else:
|
|
298
|
+
self.tui_ctx = loop.run_until_complete(TUIContext.create(detect_osc11=False))
|
|
299
|
+
except Exception:
|
|
300
|
+
self.tui_ctx = None
|
|
301
|
+
|
|
302
|
+
def _run_initialization_tasks(
|
|
303
|
+
self,
|
|
304
|
+
current_status: list[str],
|
|
305
|
+
animation_running: threading.Event,
|
|
306
|
+
status_callback: Callable[[str], None] | None = None,
|
|
307
|
+
) -> bool:
|
|
308
|
+
"""Run initialization tasks with status updates.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
current_status: Mutable list with current status message.
|
|
312
|
+
animation_running: Event to signal animation state.
|
|
313
|
+
status_callback: Optional callback to invoke when status changes.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
True if configuration is available, False otherwise.
|
|
317
|
+
"""
|
|
318
|
+
# Task 1: TUI Context.
|
|
319
|
+
current_status[0] = "Detecting terminal..."
|
|
320
|
+
if status_callback:
|
|
321
|
+
status_callback(current_status[0])
|
|
322
|
+
self._initialize_tui_context()
|
|
323
|
+
|
|
324
|
+
# Task 2: Configuration.
|
|
325
|
+
current_status[0] = "Connecting to API..."
|
|
326
|
+
if status_callback:
|
|
327
|
+
status_callback(current_status[0])
|
|
328
|
+
if not self._ensure_configuration():
|
|
329
|
+
animation_running.clear()
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
# Task 3: Updates.
|
|
333
|
+
current_status[0] = "Checking for updates..."
|
|
334
|
+
if status_callback:
|
|
335
|
+
status_callback(current_status[0])
|
|
336
|
+
self._maybe_show_update_prompt()
|
|
337
|
+
return True
|
|
338
|
+
|
|
339
|
+
def _update_pulse_step(
|
|
340
|
+
self,
|
|
341
|
+
state: AnimationState,
|
|
342
|
+
animator: LogoAnimator,
|
|
343
|
+
) -> bool:
|
|
344
|
+
"""Update pulse step and direction.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
state: Animation state container.
|
|
348
|
+
animator: LogoAnimator instance for animation.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if animation should continue, False if should stop.
|
|
352
|
+
"""
|
|
353
|
+
state.pulse_step[0] += state.pulse_direction[0] * state.step_size[0]
|
|
354
|
+
if state.pulse_step[0] >= animator.max_width + 5:
|
|
355
|
+
state.pulse_step[0] = animator.max_width + 5
|
|
356
|
+
state.pulse_direction[0] = -1
|
|
357
|
+
return not state.stop_requested.is_set()
|
|
358
|
+
if state.pulse_step[0] <= -5:
|
|
359
|
+
state.pulse_step[0] = -5
|
|
360
|
+
state.pulse_direction[0] = 1
|
|
361
|
+
return not state.stop_requested.is_set()
|
|
362
|
+
return True
|
|
363
|
+
|
|
364
|
+
def _create_animation_updater(
|
|
365
|
+
self,
|
|
366
|
+
animator: LogoAnimator,
|
|
367
|
+
state: AnimationState,
|
|
368
|
+
heading: Text,
|
|
369
|
+
) -> Callable[[Live], None]:
|
|
370
|
+
"""Create animation update function for background thread.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
animator: LogoAnimator instance for animation.
|
|
374
|
+
state: Animation state container.
|
|
375
|
+
heading: Text heading for frames.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Function to update animation in background thread.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
def build_frame(step: int, status_text: str) -> Group:
|
|
382
|
+
return Group(heading, Text(""), animator.generate_frame(step, status_text))
|
|
383
|
+
|
|
384
|
+
def update_animation(live: Live) -> None:
|
|
385
|
+
"""Update animation in background thread."""
|
|
386
|
+
while state.animation_running.is_set():
|
|
387
|
+
# Calculate next step
|
|
388
|
+
if not self._update_pulse_step(state, animator):
|
|
389
|
+
break
|
|
390
|
+
|
|
391
|
+
# Update frame with current status
|
|
392
|
+
try:
|
|
393
|
+
live.update(build_frame(state.pulse_step[0], state.current_status[0]))
|
|
394
|
+
except Exception:
|
|
395
|
+
# Animation may be stopped, ignore errors
|
|
396
|
+
break
|
|
397
|
+
time.sleep(self.ANIMATION_FRAME_DURATION)
|
|
398
|
+
state.animation_running.clear()
|
|
399
|
+
|
|
400
|
+
return update_animation
|
|
401
|
+
|
|
402
|
+
def _stop_animation_thread(
|
|
403
|
+
self,
|
|
404
|
+
animation_thread: threading.Thread,
|
|
405
|
+
state: AnimationState,
|
|
406
|
+
) -> None:
|
|
407
|
+
"""Stop animation thread gracefully.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
animation_thread: Thread running animation.
|
|
411
|
+
state: Animation state container.
|
|
412
|
+
"""
|
|
413
|
+
state.stop_requested.set()
|
|
414
|
+
state.step_size[0] = 3
|
|
415
|
+
animation_thread.join(timeout=1.5)
|
|
416
|
+
if animation_thread.is_alive():
|
|
417
|
+
state.animation_running.clear()
|
|
418
|
+
animation_thread.join(timeout=0.2)
|
|
419
|
+
|
|
420
|
+
def _run_animated_initialization(
|
|
421
|
+
self,
|
|
422
|
+
live: Live,
|
|
423
|
+
animator: LogoAnimator,
|
|
424
|
+
state: AnimationState,
|
|
425
|
+
heading: Text,
|
|
426
|
+
banner: Text,
|
|
427
|
+
) -> bool:
|
|
428
|
+
"""Run initialization tasks with animated logo.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
live: Live context for animation updates.
|
|
432
|
+
animator: LogoAnimator instance for animation.
|
|
433
|
+
state: Animation state container.
|
|
434
|
+
heading: Text heading for frames.
|
|
435
|
+
banner: Text banner for final display.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
True if configuration is available, False otherwise.
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
def build_banner() -> Group:
|
|
442
|
+
return Group(heading, Text(""), banner)
|
|
443
|
+
|
|
444
|
+
update_animation = self._create_animation_updater(
|
|
445
|
+
animator,
|
|
446
|
+
state,
|
|
447
|
+
heading,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Start animation thread.
|
|
451
|
+
animation_thread = threading.Thread(target=update_animation, args=(live,), daemon=True)
|
|
452
|
+
animation_thread.start()
|
|
453
|
+
|
|
454
|
+
# Small delay to ensure animation starts.
|
|
455
|
+
time.sleep(self.ANIMATION_STARTUP_DELAY)
|
|
456
|
+
|
|
457
|
+
# Run initialization tasks.
|
|
458
|
+
if not self._run_initialization_tasks(state.current_status, state.animation_running, status_callback=None):
|
|
459
|
+
return False
|
|
460
|
+
|
|
461
|
+
# Stop animation and show final banner.
|
|
462
|
+
self._stop_animation_thread(animation_thread, state)
|
|
463
|
+
live.update(build_banner())
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
def _run_with_animated_logo(self, animator: LogoAnimator) -> bool:
|
|
467
|
+
"""Run initialization with animated logo.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
animator: LogoAnimator instance for animation.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
True if configuration is available, False otherwise.
|
|
474
|
+
"""
|
|
475
|
+
state = AnimationState(
|
|
476
|
+
pulse_step=[0], # Use list for mutable shared state.
|
|
477
|
+
pulse_direction=[1], # Use list for mutable shared state.
|
|
478
|
+
step_size=[1],
|
|
479
|
+
current_status=[self.INITIALIZING_STATUS],
|
|
480
|
+
animation_running=threading.Event(),
|
|
481
|
+
stop_requested=threading.Event(),
|
|
482
|
+
)
|
|
483
|
+
state.animation_running.set()
|
|
484
|
+
heading = Text.from_markup(self.CLI_HEADING_MARKUP)
|
|
485
|
+
banner = Text.from_markup(self._branding.get_welcome_banner())
|
|
486
|
+
|
|
487
|
+
def build_frame(step: int, status_text: str) -> Group:
|
|
488
|
+
return Group(heading, Text(""), animator.generate_frame(step, status_text))
|
|
489
|
+
|
|
490
|
+
try:
|
|
491
|
+
with Live(
|
|
492
|
+
build_frame(0, state.current_status[0]),
|
|
493
|
+
console=self.console,
|
|
494
|
+
refresh_per_second=self.ANIMATION_FPS,
|
|
495
|
+
transient=False,
|
|
496
|
+
) as live:
|
|
497
|
+
return self._run_animated_initialization(
|
|
498
|
+
live,
|
|
499
|
+
animator,
|
|
500
|
+
state,
|
|
501
|
+
heading,
|
|
502
|
+
banner,
|
|
503
|
+
)
|
|
504
|
+
except KeyboardInterrupt:
|
|
505
|
+
# Graceful exit on Ctrl+C
|
|
506
|
+
state.animation_running.clear()
|
|
507
|
+
# Align with static path: show heading and cancellation message
|
|
508
|
+
heading = Text.from_markup(self.CLI_HEADING_MARKUP)
|
|
509
|
+
self.console.print(Group(heading, Text(""), animator.static_frame("Initialization cancelled.")))
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
def _run_with_static_logo(self, animator: LogoAnimator) -> bool:
|
|
513
|
+
"""Run initialization with static logo (non-TTY or NO_COLOR).
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
animator: LogoAnimator instance for static display.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
True if configuration is available, False otherwise.
|
|
520
|
+
"""
|
|
521
|
+
heading = Text.from_markup(self.CLI_HEADING_MARKUP)
|
|
522
|
+
banner = Text.from_markup(self._branding.get_welcome_banner())
|
|
523
|
+
|
|
524
|
+
def build_frame(status_text: str) -> Group:
|
|
525
|
+
return Group(heading, Text(""), animator.static_frame(status_text))
|
|
526
|
+
|
|
527
|
+
def build_banner() -> Group:
|
|
528
|
+
return Group(heading, Text(""), banner)
|
|
529
|
+
|
|
530
|
+
try:
|
|
531
|
+
with Live(
|
|
532
|
+
build_frame(self.INITIALIZING_STATUS),
|
|
533
|
+
console=self.console,
|
|
534
|
+
refresh_per_second=4,
|
|
535
|
+
transient=False,
|
|
536
|
+
) as live:
|
|
537
|
+
# Run initialization tasks with status updates, reusing shared logic.
|
|
538
|
+
current_status = [self.INITIALIZING_STATUS]
|
|
539
|
+
animation_running = threading.Event()
|
|
540
|
+
animation_running.set()
|
|
541
|
+
|
|
542
|
+
# Update Live display when status changes via callback.
|
|
543
|
+
def update_display(status: str) -> None:
|
|
544
|
+
"""Update Live display with current status."""
|
|
545
|
+
live.update(build_frame(status))
|
|
546
|
+
|
|
547
|
+
if not self._run_initialization_tasks(
|
|
548
|
+
current_status, animation_running, status_callback=update_display
|
|
549
|
+
):
|
|
550
|
+
return False
|
|
551
|
+
|
|
552
|
+
live.update(build_banner())
|
|
553
|
+
return True
|
|
554
|
+
except KeyboardInterrupt:
|
|
555
|
+
self.console.print(Group(heading, Text(""), animator.static_frame("Initialization cancelled.")))
|
|
556
|
+
return False
|
|
557
|
+
|
|
260
558
|
def _run_interactive_loop(self) -> None:
|
|
261
559
|
"""Run the main interactive command loop."""
|
|
262
560
|
while not self._should_exit:
|
|
@@ -1289,6 +1587,7 @@ class SlashSession:
|
|
|
1289
1587
|
*,
|
|
1290
1588
|
focus_agent: bool = False,
|
|
1291
1589
|
initial: bool = False,
|
|
1590
|
+
show_branding: bool = True,
|
|
1292
1591
|
) -> None:
|
|
1293
1592
|
"""Render the session header with branding and status.
|
|
1294
1593
|
|
|
@@ -1296,14 +1595,16 @@ class SlashSession:
|
|
|
1296
1595
|
active_agent: Optional active agent to display.
|
|
1297
1596
|
focus_agent: Whether to focus on agent display.
|
|
1298
1597
|
initial: Whether this is the initial render.
|
|
1598
|
+
show_branding: Whether to render the branding banner.
|
|
1299
1599
|
"""
|
|
1300
1600
|
if focus_agent and active_agent is not None:
|
|
1301
1601
|
self._render_focused_agent_header(active_agent)
|
|
1302
1602
|
return
|
|
1303
1603
|
|
|
1304
1604
|
full_header = initial or not self._welcome_rendered
|
|
1305
|
-
if full_header:
|
|
1605
|
+
if full_header and show_branding:
|
|
1306
1606
|
self._render_branding_banner()
|
|
1607
|
+
if full_header:
|
|
1307
1608
|
self.console.rule(style=PRIMARY)
|
|
1308
1609
|
self._render_main_header(active_agent, full=full_header)
|
|
1309
1610
|
if full_header:
|
|
@@ -1313,7 +1614,7 @@ class SlashSession:
|
|
|
1313
1614
|
def _render_branding_banner(self) -> None:
|
|
1314
1615
|
"""Render the GL AIP branding banner."""
|
|
1315
1616
|
banner = self._branding.get_welcome_banner()
|
|
1316
|
-
heading =
|
|
1617
|
+
heading = self.CLI_HEADING_MARKUP
|
|
1317
1618
|
self.console.print(heading)
|
|
1318
1619
|
self.console.print()
|
|
1319
1620
|
self.console.print(banner)
|
|
@@ -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;
|
|
@@ -86,3 +98,63 @@
|
|
|
86
98
|
#form-actions {
|
|
87
99
|
margin: 0 1 0 1;
|
|
88
100
|
}
|
|
101
|
+
|
|
102
|
+
/* Harlequin Layout Styling */
|
|
103
|
+
|
|
104
|
+
#left-pane-title, #right-pane-title {
|
|
105
|
+
padding: 1;
|
|
106
|
+
text-style: bold;
|
|
107
|
+
border-bottom: solid $primary;
|
|
108
|
+
height: 3;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#harlequin-filter {
|
|
112
|
+
padding: 0 1;
|
|
113
|
+
margin: 1;
|
|
114
|
+
height: 3;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#harlequin-accounts-list {
|
|
118
|
+
padding: 0 1;
|
|
119
|
+
margin: 1;
|
|
120
|
+
height: 1fr;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#left-content {
|
|
124
|
+
height: 100%;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#right-content {
|
|
128
|
+
padding: 1;
|
|
129
|
+
height: 100%;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.detail-label {
|
|
133
|
+
padding: 0 1 0 0;
|
|
134
|
+
text-style: bold;
|
|
135
|
+
width: 12;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#detail-fields {
|
|
139
|
+
padding: 1 0;
|
|
140
|
+
height: auto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#harlequin-detail-url,
|
|
144
|
+
#harlequin-detail-key,
|
|
145
|
+
#harlequin-detail-status {
|
|
146
|
+
padding: 0 1;
|
|
147
|
+
margin-bottom: 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#harlequin-detail-actions {
|
|
151
|
+
padding: 1 0;
|
|
152
|
+
margin-top: 1;
|
|
153
|
+
height: auto;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#harlequin-status {
|
|
157
|
+
padding: 1;
|
|
158
|
+
margin-top: 1;
|
|
159
|
+
height: auto;
|
|
160
|
+
}
|