glaip-sdk 0.0.9__py3-none-any.whl → 0.0.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.
@@ -29,6 +29,7 @@ from .prompt import (
29
29
  Style,
30
30
  patch_stdout,
31
31
  setup_prompt_toolkit,
32
+ to_formatted_text,
32
33
  )
33
34
 
34
35
  SlashHandler = Callable[["SlashSession", list[str], bool], bool]
@@ -61,6 +62,8 @@ class SlashSession:
61
62
  self._interactive = bool(sys.stdin.isatty() and sys.stdout.isatty())
62
63
  self._config_cache: dict[str, Any] | None = None
63
64
  self._welcome_rendered = False
65
+ self._verbose_enabled = False
66
+ self._active_renderer: Any | None = None
64
67
 
65
68
  self._home_placeholder = "Start with / to browse commands"
66
69
 
@@ -73,6 +76,8 @@ class SlashSession:
73
76
  self._setup_prompt_toolkit()
74
77
  self._register_defaults()
75
78
  self._branding = AIPBranding.create_from_sdk()
79
+ self._suppress_login_layout = False
80
+ self._default_actions_shown = False
76
81
 
77
82
  # ------------------------------------------------------------------
78
83
  # Session orchestration
@@ -93,7 +98,9 @@ class SlashSession:
93
98
  if not self._ensure_configuration():
94
99
  return
95
100
 
96
- self._render_header(initial=True)
101
+ self._render_header(initial=not self._welcome_rendered)
102
+ if not self._default_actions_shown:
103
+ self._show_default_quick_actions()
97
104
  self._render_home_hint()
98
105
  self._run_interactive_loop()
99
106
 
@@ -151,6 +158,7 @@ class SlashSession:
151
158
  self.console.print(
152
159
  "[yellow]Configuration required.[/] Launching `/login` wizard..."
153
160
  )
161
+ self._suppress_login_layout = True
154
162
  try:
155
163
  self._cmd_login([], False)
156
164
  except KeyboardInterrupt:
@@ -158,6 +166,8 @@ class SlashSession:
158
166
  "[red]Configuration aborted. Closing the command palette.[/red]"
159
167
  )
160
168
  return False
169
+ finally:
170
+ self._suppress_login_layout = False
161
171
 
162
172
  return True
163
173
 
@@ -209,58 +219,60 @@ class SlashSession:
209
219
  def _cmd_help(self, _args: list[str], invoked_from_agent: bool) -> bool:
210
220
  try:
211
221
  if invoked_from_agent:
212
- table = Table(title="Agent Context")
213
- table.add_column("Input", style="cyan", no_wrap=True)
214
- table.add_column("What happens", style="green")
215
- table.add_row(
216
- "<message>", "Run the active agent once with that prompt."
217
- )
218
- table.add_row("/details", "Show the full agent export and metadata.")
219
- table.add_row(
220
- self.STATUS_COMMAND, "Display connection status without leaving."
221
- )
222
- table.add_row("/exit (/back)", "Return to the slash home screen.")
223
- table.add_row("/help (/?)", "Display this context-aware menu.")
224
- self.console.print(table)
225
- if self.last_run_input:
226
- self.console.print(f"[dim]Last run input:[/] {self.last_run_input}")
227
- self.console.print(
228
- "[dim]Global commands (e.g. `/login`, `/status`) remain available inside the agent prompt.[/dim]"
229
- )
222
+ self._render_agent_help()
230
223
  else:
231
- table = Table(title="Slash Commands")
232
- table.add_column("Command", style="cyan", no_wrap=True)
233
- table.add_column("Description", style="green")
234
-
235
- for cmd in sorted(self._unique_commands.values(), key=lambda c: c.name):
236
- aliases = ", ".join(f"/{alias}" for alias in cmd.aliases if alias)
237
- verb = f"/{cmd.name}"
238
- if aliases:
239
- verb = f"{verb} ({aliases})"
240
- table.add_row(verb, cmd.help)
241
-
242
- self.console.print(table)
243
- self.console.print(
244
- "[dim]Tip: `{self.AGENTS_COMMAND}` lets you jump into an agent run prompt quickly.[/dim]"
245
- )
224
+ self._render_global_help()
246
225
  except Exception as exc: # pragma: no cover - UI/display errors
247
226
  self.console.print(f"[red]Error displaying help: {exc}[/red]")
248
227
  return False
249
228
 
250
229
  return True
251
230
 
231
+ def _render_agent_help(self) -> None:
232
+ table = Table(title="Agent Context")
233
+ table.add_column("Input", style="cyan", no_wrap=True)
234
+ table.add_column("What happens", style="green")
235
+ table.add_row("<message>", "Run the active agent once with that prompt.")
236
+ table.add_row("/details", "Show the full agent export and metadata.")
237
+ table.add_row(self.STATUS_COMMAND, "Display connection status without leaving.")
238
+ table.add_row("/verbose", "Toggle verbose streaming output (Ctrl+T works too).")
239
+ table.add_row("/exit (/back)", "Return to the slash home screen.")
240
+ table.add_row("/help (/?)", "Display this context-aware menu.")
241
+ self.console.print(table)
242
+ if self.last_run_input:
243
+ self.console.print(f"[dim]Last run input:[/] {self.last_run_input}")
244
+ self.console.print(
245
+ "[dim]Global commands (e.g. `/login`, `/status`) remain available inside the agent prompt.[/dim]"
246
+ )
247
+
248
+ def _render_global_help(self) -> None:
249
+ table = Table(title="Slash Commands")
250
+ table.add_column("Command", style="cyan", no_wrap=True)
251
+ table.add_column("Description", style="green")
252
+
253
+ for cmd in sorted(self._unique_commands.values(), key=lambda c: c.name):
254
+ aliases = ", ".join(f"/{alias}" for alias in cmd.aliases if alias)
255
+ verb = f"/{cmd.name}"
256
+ if aliases:
257
+ verb = f"{verb} ({aliases})"
258
+ table.add_row(verb, cmd.help)
259
+
260
+ self.console.print(table)
261
+ self.console.print(
262
+ "[dim]Tip: `{self.AGENTS_COMMAND}` lets you jump into an agent run prompt quickly.[/dim]"
263
+ )
264
+
252
265
  def _cmd_login(self, _args: list[str], _invoked_from_agent: bool) -> bool:
253
266
  self.console.print("[cyan]Launching configuration wizard...[/cyan]")
254
267
  try:
255
268
  self.ctx.invoke(configure_command)
256
269
  self._config_cache = None
257
- self._render_header(initial=True)
258
- self._show_quick_actions(
259
- [
260
- (self.STATUS_COMMAND, "Verify the connection"),
261
- (self.AGENTS_COMMAND, "Pick an agent to inspect or run"),
262
- ]
263
- )
270
+ if self._suppress_login_layout:
271
+ self._welcome_rendered = False
272
+ self._default_actions_shown = False
273
+ else:
274
+ self._render_header(initial=True)
275
+ self._show_default_quick_actions()
264
276
  except click.ClickException as exc:
265
277
  self.console.print(f"[red]{exc}[/red]")
266
278
  return True
@@ -343,7 +355,7 @@ class SlashSession:
343
355
  # running.
344
356
  return True
345
357
 
346
- self.console.print("[cyan]Closing the command palette.")
358
+ self.console.print("[cyan]Closing the command palette.[/cyan]")
347
359
  return False
348
360
 
349
361
  # ------------------------------------------------------------------
@@ -388,12 +400,119 @@ class SlashSession:
388
400
  aliases=("q",),
389
401
  )
390
402
  )
403
+ self._register(
404
+ SlashCommand(
405
+ name="verbose",
406
+ help="Toggle verbose streaming output.",
407
+ handler=SlashSession._cmd_verbose,
408
+ )
409
+ )
391
410
 
392
411
  def _register(self, command: SlashCommand) -> None:
393
412
  self._unique_commands[command.name] = command
394
413
  for key in (command.name, *command.aliases):
395
414
  self._commands[key] = command
396
415
 
416
+ # ------------------------------------------------------------------
417
+ # Verbose mode helpers
418
+ # ------------------------------------------------------------------
419
+ @property
420
+ def verbose_enabled(self) -> bool:
421
+ """Return whether verbose agent runs are enabled."""
422
+
423
+ return self._verbose_enabled
424
+
425
+ def set_verbose(self, enabled: bool, *, announce: bool = True) -> None:
426
+ """Enable or disable verbose mode with optional announcement."""
427
+
428
+ if self._verbose_enabled == enabled:
429
+ if announce:
430
+ self._print_verbose_status(context="already")
431
+ return
432
+
433
+ self._verbose_enabled = enabled
434
+ self._sync_active_renderer()
435
+ if announce:
436
+ self._print_verbose_status(context="changed")
437
+
438
+ def toggle_verbose(self, *, announce: bool = True) -> None:
439
+ """Flip verbose mode state."""
440
+
441
+ self.set_verbose(not self._verbose_enabled, announce=announce)
442
+
443
+ def _cmd_verbose(self, args: list[str], _invoked_from_agent: bool) -> bool:
444
+ """Slash handler for `/verbose` command."""
445
+
446
+ if args:
447
+ self.console.print(
448
+ "Usage: `/verbose` toggles verbose streaming output. Press Ctrl+T as a shortcut."
449
+ )
450
+ else:
451
+ self.toggle_verbose()
452
+
453
+ return True
454
+
455
+ def _print_verbose_status(self, *, context: str) -> None:
456
+ state_word = "on" if self._verbose_enabled else "off"
457
+ if context == "already":
458
+ self.console.print(
459
+ f"Verbose mode already {state_word}. Use Ctrl+T or `/verbose` to toggle."
460
+ )
461
+ return
462
+
463
+ change_word = "enabled" if self._verbose_enabled else "disabled"
464
+ self.console.print(
465
+ f"Verbose mode {change_word}. Use Ctrl+T or `/verbose` to toggle."
466
+ )
467
+
468
+ # ------------------------------------------------------------------
469
+ # Agent run coordination helpers
470
+ # ------------------------------------------------------------------
471
+ def register_active_renderer(self, renderer: Any) -> None:
472
+ """Register the renderer currently streaming an agent run."""
473
+
474
+ self._active_renderer = renderer
475
+ self._sync_active_renderer()
476
+
477
+ def clear_active_renderer(self, renderer: Any | None = None) -> None:
478
+ """Clear the active renderer if it matches the provided instance."""
479
+
480
+ if renderer is not None and renderer is not self._active_renderer:
481
+ return
482
+ self._active_renderer = None
483
+
484
+ def notify_agent_run_started(self) -> None:
485
+ """Mark that an agent run is in progress."""
486
+
487
+ self.clear_active_renderer()
488
+
489
+ def notify_agent_run_finished(self) -> None:
490
+ """Mark that the active agent run has completed."""
491
+
492
+ self.clear_active_renderer()
493
+
494
+ def _sync_active_renderer(self) -> None:
495
+ """Ensure the active renderer reflects the current verbose state."""
496
+
497
+ renderer = self._active_renderer
498
+ if renderer is None:
499
+ return
500
+
501
+ applied = False
502
+ apply_verbose = getattr(renderer, "apply_verbosity", None)
503
+ if callable(apply_verbose):
504
+ try:
505
+ apply_verbose(self._verbose_enabled)
506
+ applied = True
507
+ except Exception:
508
+ pass
509
+
510
+ if not applied and hasattr(renderer, "verbose"):
511
+ try:
512
+ renderer.verbose = self._verbose_enabled
513
+ except Exception:
514
+ pass
515
+
397
516
  def _parse(self, raw: str) -> tuple[str, list[str]]:
398
517
  try:
399
518
  tokens = shlex.split(raw)
@@ -416,33 +535,83 @@ class SlashSession:
416
535
  match = get_close_matches(verb, keys, n=1)
417
536
  return match[0] if match else None
418
537
 
419
- def _prompt(self, message: str, *, placeholder: str | None = None) -> str:
420
- if self._ptk_session and self._ptk_style and patch_stdout:
421
- with patch_stdout(): # pragma: no cover - UI specific
422
- prompt_text = (
423
- FormattedText([("class:prompt", message)])
424
- if FormattedText is not None
425
- else message
426
- )
427
- prompt_kwargs: dict[str, Any] = {"style": self._ptk_style}
428
- if placeholder:
429
- placeholder_text = (
430
- FormattedText([("class:placeholder", placeholder)])
431
- if FormattedText is not None
432
- else placeholder
433
- )
434
- prompt_kwargs["placeholder"] = placeholder_text
435
- try:
436
- return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
437
- except (
438
- TypeError
439
- ): # pragma: no cover - compatibility with older prompt_toolkit
440
- prompt_kwargs.pop("placeholder", None)
441
- return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
538
+ def _convert_message(self, value: Any) -> Any:
539
+ """Convert a message value to the appropriate format for display."""
540
+ if FormattedText is not None and to_formatted_text is not None:
541
+ return to_formatted_text(value)
542
+ if FormattedText is not None:
543
+ return FormattedText([("class:prompt", str(value))])
544
+ return str(value)
545
+
546
+ def _get_prompt_kwargs(self, placeholder: str | None) -> dict[str, Any]:
547
+ """Get prompt kwargs with optional placeholder styling."""
548
+ prompt_kwargs: dict[str, Any] = {"style": self._ptk_style}
549
+ if placeholder:
550
+ placeholder_text = (
551
+ FormattedText([("class:placeholder", placeholder)])
552
+ if FormattedText is not None
553
+ else placeholder
554
+ )
555
+ prompt_kwargs["placeholder"] = placeholder_text
556
+ return prompt_kwargs
557
+
558
+ def _prompt_with_prompt_toolkit(
559
+ self, message: str | Callable[[], Any], placeholder: str | None
560
+ ) -> str:
561
+ """Handle prompting with prompt_toolkit."""
562
+ with patch_stdout(): # pragma: no cover - UI specific
563
+ if callable(message):
564
+
565
+ def prompt_text() -> Any:
566
+ return self._convert_message(message())
567
+ else:
568
+ prompt_text = self._convert_message(message)
569
+
570
+ prompt_kwargs = self._get_prompt_kwargs(placeholder)
442
571
 
572
+ try:
573
+ return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
574
+ except (
575
+ TypeError
576
+ ): # pragma: no cover - compatibility with older prompt_toolkit
577
+ prompt_kwargs.pop("placeholder", None)
578
+ return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
579
+
580
+ def _extract_message_text(self, raw_value: Any) -> str:
581
+ """Extract text content from various message formats."""
582
+ if isinstance(raw_value, str):
583
+ return raw_value
584
+
585
+ try:
586
+ if FormattedText is not None and isinstance(raw_value, FormattedText):
587
+ return "".join(text for _style, text in raw_value)
588
+ elif isinstance(raw_value, list):
589
+ return "".join(segment[1] for segment in raw_value)
590
+ else:
591
+ return str(raw_value)
592
+ except Exception:
593
+ return str(raw_value)
594
+
595
+ def _prompt_with_basic_input(
596
+ self, message: str | Callable[[], Any], placeholder: str | None
597
+ ) -> str:
598
+ """Handle prompting with basic input."""
443
599
  if placeholder:
444
600
  self.console.print(f"[dim]{placeholder}[/dim]")
445
- return input(message)
601
+
602
+ raw_value = message() if callable(message) else message
603
+ actual_message = self._extract_message_text(raw_value)
604
+
605
+ return input(actual_message)
606
+
607
+ def _prompt(
608
+ self, message: str | Callable[[], Any], *, placeholder: str | None = None
609
+ ) -> str:
610
+ """Main prompt function with reduced complexity."""
611
+ if self._ptk_session and self._ptk_style and patch_stdout:
612
+ return self._prompt_with_prompt_toolkit(message, placeholder)
613
+
614
+ return self._prompt_with_basic_input(message, placeholder)
446
615
 
447
616
  def _get_client(self) -> Any: # type: ignore[no-any-return]
448
617
  if self._client is None:
@@ -503,12 +672,17 @@ class SlashSession:
503
672
  agent_type = getattr(active_agent, "type", "") or "-"
504
673
  description = getattr(active_agent, "description", "") or ""
505
674
 
675
+ verbose_label = "verbose on" if self._verbose_enabled else "verbose off"
676
+
506
677
  header_grid = Table.grid(expand=True)
507
678
  header_grid.add_column(ratio=3)
508
679
  header_grid.add_column(ratio=1, justify="right")
509
680
 
510
681
  primary_line = f"[bold]{agent_name}[/bold] · [dim]{agent_type}[/dim] · [cyan]{agent_id}[/cyan]"
511
- header_grid.add_row(primary_line, "[green]ready[/green]")
682
+ header_grid.add_row(
683
+ primary_line,
684
+ f"[green]ready[/green] · {verbose_label}",
685
+ )
512
686
 
513
687
  if description:
514
688
  header_grid.add_row(f"[dim]{description}[/dim]", "")
@@ -517,10 +691,12 @@ class SlashSession:
517
691
  keybar.add_column(justify="left")
518
692
  keybar.add_column(justify="left")
519
693
  keybar.add_column(justify="left")
694
+ keybar.add_column(justify="left")
520
695
  keybar.add_row(
521
696
  "[bold]/help[/bold] [dim]Show commands[/dim]",
522
697
  "[bold]/details[/bold] [dim]Agent config[/dim]",
523
698
  "[bold]/exit[/bold] [dim]Back[/dim]",
699
+ "[bold]Ctrl+T[/bold] [dim]Toggle verbose[/dim]",
524
700
  )
525
701
 
526
702
  header_grid.add_row(keybar, "")
@@ -544,6 +720,8 @@ class SlashSession:
544
720
  "",
545
721
  f"[dim]API URL[/dim]: {api_url or 'Not configured'}",
546
722
  f"[dim]Credentials[/dim]: {status}",
723
+ f"[dim]Verbose mode[/dim]: {'on' if self._verbose_enabled else 'off'}",
724
+ "[dim]Tip[/dim]: Press Ctrl+T or run `/verbose` to toggle verbose streaming.",
547
725
  ]
548
726
  extra: list[str] = []
549
727
  self._add_agent_info_to_header(extra, active_agent)
@@ -560,8 +738,10 @@ class SlashSession:
560
738
  status_bar.add_row(
561
739
  "[bold cyan]AIP Palette[/bold cyan]",
562
740
  f"[dim]API[/dim]: {api_url or 'Not configured'}",
563
- "[dim]Type /help for shortcuts[/dim]",
741
+ f"[dim]Verbose[/dim]: {'on' if self._verbose_enabled else 'off'}",
564
742
  )
743
+ status_bar.add_row("[dim]Ctrl+T toggles verbose[/dim]", "", "")
744
+ status_bar.add_row("[dim]Type /help for shortcuts[/dim]", "", "")
565
745
 
566
746
  if active_agent is not None:
567
747
  agent_id = str(getattr(active_agent, "id", ""))
@@ -598,10 +778,20 @@ class SlashSession:
598
778
  label = recent.get("name") or recent.get("id") or "-"
599
779
  lines.append(f"[dim]Recent agent[/dim]: {label} [{recent.get('id', '-')}]")
600
780
 
781
+ def _show_default_quick_actions(self) -> None:
782
+ self._show_quick_actions(
783
+ [
784
+ (self.STATUS_COMMAND, "Verify the connection"),
785
+ (self.AGENTS_COMMAND, "Pick an agent to inspect or run"),
786
+ ]
787
+ )
788
+ self._default_actions_shown = True
789
+
601
790
  def _render_home_hint(self) -> None:
602
791
  self.console.print(
603
792
  AIPPanel(
604
793
  "Type `/help` for command palette commands, `/agents` to browse agents, or `/exit` (`/q`) to leave the palette.\n"
794
+ "Press Ctrl+T to toggle verbose output.\n"
605
795
  "Press Ctrl+C to cancel the current entry, Ctrl+D to quit immediately.",
606
796
  title="✨ Getting Started",
607
797
  border_style="cyan",
@@ -0,0 +1,107 @@
1
+ """Utility helpers for checking and displaying SDK update notifications.
2
+
3
+ Author:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from collections.abc import Callable
11
+
12
+ import httpx
13
+ from packaging.version import InvalidVersion, Version
14
+ from rich.console import Console
15
+
16
+ from glaip_sdk.rich_components import AIPPanel
17
+
18
+ FetchLatestVersion = Callable[[], str | None]
19
+
20
+ PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
21
+ DEFAULT_TIMEOUT = 1.5 # seconds
22
+
23
+
24
+ def _parse_version(value: str) -> Version | None:
25
+ """Parse a version string into a `Version`, returning None on failure."""
26
+ try:
27
+ return Version(value)
28
+ except InvalidVersion:
29
+ return None
30
+
31
+
32
+ def _fetch_latest_version(package_name: str) -> str | None:
33
+ """Fetch the latest published version from PyPI."""
34
+ url = PYPI_JSON_URL.format(package=package_name)
35
+ timeout = httpx.Timeout(DEFAULT_TIMEOUT)
36
+
37
+ try:
38
+ with httpx.Client(timeout=timeout) as client:
39
+ response = client.get(url, headers={"Accept": "application/json"})
40
+ response.raise_for_status()
41
+ payload = response.json()
42
+ except httpx.HTTPError:
43
+ return None
44
+ except ValueError:
45
+ return None
46
+
47
+ info = payload.get("info") if isinstance(payload, dict) else None
48
+ latest_version = info.get("version") if isinstance(info, dict) else None
49
+ if isinstance(latest_version, str) and latest_version.strip():
50
+ return latest_version.strip()
51
+ return None
52
+
53
+
54
+ def _should_check_for_updates() -> bool:
55
+ """Return False when update checks are explicitly disabled."""
56
+ return os.getenv("AIP_NO_UPDATE_CHECK") is None
57
+
58
+
59
+ def _build_update_panel(
60
+ current_version: str,
61
+ latest_version: str,
62
+ ) -> AIPPanel:
63
+ """Create a Rich panel that prompts the user to update."""
64
+ message = (
65
+ f"[bold yellow]✨ Update available![/bold yellow] "
66
+ f"{current_version} → {latest_version}\n\n"
67
+ "See the latest release notes:\n"
68
+ f"https://pypi.org/project/glaip-sdk/{latest_version}/\n\n"
69
+ "[cyan]Run[/cyan] [bold]aip update[/bold] to install."
70
+ )
71
+ return AIPPanel(
72
+ message,
73
+ title="[bold green]AIP SDK Update[/bold green]",
74
+ )
75
+
76
+
77
+ def maybe_notify_update(
78
+ current_version: str,
79
+ *,
80
+ package_name: str = "glaip-sdk",
81
+ console: Console | None = None,
82
+ fetch_latest_version: FetchLatestVersion | None = None,
83
+ ) -> None:
84
+ """Check PyPI for a newer version and display a prompt if one exists.
85
+
86
+ This function deliberately swallows network errors to avoid impacting CLI
87
+ startup time when offline or when PyPI is unavailable.
88
+ """
89
+ if not _should_check_for_updates():
90
+ return
91
+
92
+ fetcher = fetch_latest_version or (lambda: _fetch_latest_version(package_name))
93
+ latest_version = fetcher()
94
+ if not latest_version:
95
+ return
96
+
97
+ current = _parse_version(current_version)
98
+ latest = _parse_version(latest_version)
99
+ if current is None or latest is None or latest <= current:
100
+ return
101
+
102
+ active_console = console or Console()
103
+ panel = _build_update_panel(current_version, latest_version)
104
+ active_console.print(panel)
105
+
106
+
107
+ __all__ = ["maybe_notify_update"]
glaip_sdk/cli/utils.py CHANGED
@@ -828,7 +828,7 @@ def _build_table_group(
828
828
  table = _create_table(columns, title)
829
829
  for row in rows:
830
830
  table.add_row(*[str(row.get(key, "N/A")) for key, _, _, _ in columns])
831
- footer = Text(f"\n[dim]Total {len(rows)} items[/dim]")
831
+ footer = Text.from_markup(f"\n[dim]Total {len(rows)} items[/dim]")
832
832
  return Group(table, footer)
833
833
 
834
834
 
@@ -872,7 +872,7 @@ def _handle_markdown_output(
872
872
 
873
873
  def _handle_empty_items(title: str) -> None:
874
874
  """Handle case when no items are found."""
875
- console.print(Text(f"[yellow]No {title.lower()} found.[/yellow]"))
875
+ console.print(Text.from_markup(f"[yellow]No {title.lower()} found.[/yellow]"))
876
876
 
877
877
 
878
878
  def _should_use_fuzzy_picker() -> bool:
@@ -916,7 +916,9 @@ def _print_selection_tip(title: str) -> None:
916
916
  """Print the contextual follow-up tip after a fuzzy selection."""
917
917
 
918
918
  tip_cmd = _resource_tip_command(title)
919
- console.print(Text(f"\n[dim]Tip: use `{tip_cmd} <ID>` for details[/dim]"))
919
+ console.print(
920
+ Text.from_markup(f"\n[dim]Tip: use `{tip_cmd} <ID>` for details[/dim]")
921
+ )
920
922
 
921
923
 
922
924
  def _handle_fuzzy_pick_selection(
@@ -936,11 +938,21 @@ def _handle_fuzzy_pick_selection(
936
938
 
937
939
 
938
940
  def _handle_table_output(
939
- rows: list[dict[str, Any]], columns: list[tuple], title: str
941
+ rows: list[dict[str, Any]],
942
+ columns: list[tuple],
943
+ title: str,
944
+ *,
945
+ use_pager: bool | None = None,
940
946
  ) -> None:
941
947
  """Handle table output with paging."""
942
948
  content = _build_table_group(rows, columns, title)
943
- if _should_page_output(len(rows), console.is_terminal and os.isatty(1)):
949
+ should_page = (
950
+ _should_page_output(len(rows), console.is_terminal and os.isatty(1))
951
+ if use_pager is None
952
+ else use_pager
953
+ )
954
+
955
+ if should_page:
944
956
  ansi = _render_ansi(content)
945
957
  if not _page_with_system_pager(ansi):
946
958
  with console.pager(styles=True):
@@ -955,8 +967,11 @@ def output_list(
955
967
  title: str,
956
968
  columns: list[tuple[str, str, str, int | None]],
957
969
  transform_func: Callable | None = None,
970
+ *,
971
+ skip_picker: bool = False,
972
+ use_pager: bool | None = None,
958
973
  ) -> None:
959
- """Display a list with fuzzy palette by default on TTY, Rich table as fallback."""
974
+ """Display a list with optional fuzzy palette for quick selection."""
960
975
  fmt = _get_view(ctx)
961
976
  rows = _normalise_rows(items, transform_func)
962
977
  rows = _mask_rows_if_configured(rows)
@@ -983,10 +998,10 @@ def output_list(
983
998
  except Exception:
984
999
  pass
985
1000
 
986
- if _handle_fuzzy_pick_selection(rows, columns, title):
1001
+ if not skip_picker and _handle_fuzzy_pick_selection(rows, columns, title):
987
1002
  return
988
1003
 
989
- _handle_table_output(rows, columns, title)
1004
+ _handle_table_output(rows, columns, title, use_pager=use_pager)
990
1005
 
991
1006
 
992
1007
  # ------------------------- Output flags decorator ------------------------ #
@@ -1108,6 +1123,17 @@ def build_renderer(
1108
1123
  verbose=verbose,
1109
1124
  )
1110
1125
 
1126
+ # Link the renderer back to the slash session when running from the palette.
1127
+ try:
1128
+ ctx_obj = getattr(_ctx, "obj", None)
1129
+ if isinstance(ctx_obj, dict):
1130
+ session = ctx_obj.get("_slash_session")
1131
+ if session and hasattr(session, "register_active_renderer"):
1132
+ session.register_active_renderer(renderer)
1133
+ except Exception:
1134
+ # Never let session bookkeeping break renderer creation
1135
+ pass
1136
+
1111
1137
  return renderer, working_console
1112
1138
 
1113
1139
 
@@ -6,10 +6,6 @@ Authors:
6
6
 
7
7
  # Default language model configuration
8
8
  DEFAULT_MODEL = "gpt-4.1"
9
- DEFAULT_MODEL_PROVIDER = "openai"
10
-
11
- # Default timeout values
12
- DEFAULT_TIMEOUT = 30.0
13
9
  DEFAULT_AGENT_RUN_TIMEOUT = 300
14
10
 
15
11
  # User agent and version
glaip_sdk/exceptions.py CHANGED
@@ -98,9 +98,3 @@ class AgentTimeoutError(TimeoutError):
98
98
  super().__init__(message)
99
99
  self.timeout_seconds = timeout_seconds
100
100
  self.agent_name = agent_name
101
-
102
-
103
- class ClientError(APIError):
104
- """Client-side error (e.g., invalid request format, missing parameters)."""
105
-
106
- pass