glaip-sdk 0.0.19__py3-none-any.whl → 0.1.0__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.
Files changed (56) hide show
  1. glaip_sdk/_version.py +2 -2
  2. glaip_sdk/branding.py +27 -2
  3. glaip_sdk/cli/auth.py +93 -28
  4. glaip_sdk/cli/commands/__init__.py +2 -2
  5. glaip_sdk/cli/commands/agents.py +127 -21
  6. glaip_sdk/cli/commands/configure.py +141 -90
  7. glaip_sdk/cli/commands/mcps.py +82 -31
  8. glaip_sdk/cli/commands/models.py +4 -3
  9. glaip_sdk/cli/commands/tools.py +27 -14
  10. glaip_sdk/cli/commands/update.py +66 -0
  11. glaip_sdk/cli/config.py +13 -2
  12. glaip_sdk/cli/display.py +35 -26
  13. glaip_sdk/cli/io.py +14 -5
  14. glaip_sdk/cli/main.py +185 -73
  15. glaip_sdk/cli/pager.py +2 -1
  16. glaip_sdk/cli/resolution.py +4 -1
  17. glaip_sdk/cli/slash/__init__.py +3 -4
  18. glaip_sdk/cli/slash/agent_session.py +88 -36
  19. glaip_sdk/cli/slash/prompt.py +20 -48
  20. glaip_sdk/cli/slash/session.py +437 -189
  21. glaip_sdk/cli/transcript/__init__.py +71 -0
  22. glaip_sdk/cli/transcript/cache.py +338 -0
  23. glaip_sdk/cli/transcript/capture.py +278 -0
  24. glaip_sdk/cli/transcript/export.py +38 -0
  25. glaip_sdk/cli/transcript/launcher.py +79 -0
  26. glaip_sdk/cli/transcript/viewer.py +794 -0
  27. glaip_sdk/cli/update_notifier.py +29 -5
  28. glaip_sdk/cli/utils.py +255 -74
  29. glaip_sdk/client/agents.py +3 -1
  30. glaip_sdk/client/run_rendering.py +126 -21
  31. glaip_sdk/icons.py +25 -0
  32. glaip_sdk/models.py +6 -0
  33. glaip_sdk/rich_components.py +29 -1
  34. glaip_sdk/utils/__init__.py +1 -1
  35. glaip_sdk/utils/client_utils.py +6 -4
  36. glaip_sdk/utils/display.py +61 -32
  37. glaip_sdk/utils/rendering/formatting.py +55 -11
  38. glaip_sdk/utils/rendering/models.py +15 -2
  39. glaip_sdk/utils/rendering/renderer/__init__.py +0 -2
  40. glaip_sdk/utils/rendering/renderer/base.py +1287 -227
  41. glaip_sdk/utils/rendering/renderer/config.py +3 -5
  42. glaip_sdk/utils/rendering/renderer/debug.py +73 -16
  43. glaip_sdk/utils/rendering/renderer/panels.py +27 -15
  44. glaip_sdk/utils/rendering/renderer/progress.py +61 -38
  45. glaip_sdk/utils/rendering/renderer/stream.py +3 -3
  46. glaip_sdk/utils/rendering/renderer/toggle.py +184 -0
  47. glaip_sdk/utils/rendering/step_tree_state.py +102 -0
  48. glaip_sdk/utils/rendering/steps.py +944 -16
  49. glaip_sdk/utils/serialization.py +5 -2
  50. glaip_sdk/utils/validation.py +1 -2
  51. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.1.0.dist-info}/METADATA +12 -1
  52. glaip_sdk-0.1.0.dist-info/RECORD +82 -0
  53. glaip_sdk/utils/rich_utils.py +0 -29
  54. glaip_sdk-0.0.19.dist-info/RECORD +0 -73
  55. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.1.0.dist-info}/WHEEL +0 -0
  56. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.1.0.dist-info}/entry_points.txt +0 -0
glaip_sdk/_version.py CHANGED
@@ -6,6 +6,8 @@ Falls back to a dev marker when running from source without installation.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ from pathlib import Path
10
+
9
11
  try:
10
12
  from importlib.metadata import PackageNotFoundError, version # Python 3.8+
11
13
  except Exception: # pragma: no cover - extremely unlikely
@@ -38,8 +40,6 @@ def _try_get_dev_version() -> str | None:
38
40
  return None
39
41
 
40
42
  try:
41
- from pathlib import Path
42
-
43
43
  here = Path(__file__).resolve()
44
44
  root = here.parent.parent # project root (contains pyproject.toml)
45
45
  pyproject = root / "pyproject.toml"
glaip_sdk/branding.py CHANGED
@@ -36,9 +36,27 @@ SECONDARY_DARK = "#003A5C" # Darkest variant for emphasis
36
36
  SECONDARY_MEDIUM = "#005CB8" # Medium variant for UI elements
37
37
  SECONDARY_LIGHT = "#40B4E5" # Light variant for highlights
38
38
 
39
+ # Neutral companion palette (optimized for dark terminals)
40
+ SUCCESS = "#7FA089" # Muted teal-green for success messaging
41
+ WARNING = "#C3A46F" # Soft amber for warnings
42
+ ERROR = "#C97B6C" # Tempered coral-red for errors
43
+ INFO = "#9CA3AF" # Cool grey for informational accents
44
+ NEUTRAL = "#D1D5DB" # Light grey for muted text and dividers
45
+
39
46
  BORDER = PRIMARY # Keep borders aligned with primary brand tone
40
47
  TITLE_STYLE = f"bold {PRIMARY}"
41
48
  LABEL = "bold"
49
+ SUCCESS_STYLE = f"bold {SUCCESS}"
50
+ WARNING_STYLE = f"bold {WARNING}"
51
+ ERROR_STYLE = f"bold {ERROR}"
52
+ INFO_STYLE = f"bold {INFO}"
53
+ ACCENT_STYLE = INFO # For subdued inline highlights
54
+
55
+ # Hint styling (slash command helpers, tips, quick actions)
56
+ HINT_TITLE_STYLE = f"bold {SECONDARY_LIGHT}"
57
+ HINT_COMMAND_STYLE = f"bold {SECONDARY_LIGHT}"
58
+ HINT_DESCRIPTION_COLOR = NEUTRAL
59
+ HINT_PREFIX_STYLE = INFO_STYLE
42
60
 
43
61
 
44
62
  class AIPBranding:
@@ -118,11 +136,17 @@ GDP Labs AI Agents Package
118
136
  "architecture": platform.architecture()[0],
119
137
  }
120
138
 
121
- def display_welcome_panel(self, title: str = "Welcome to AIP") -> None:
139
+ def display_welcome_panel(
140
+ self,
141
+ title: str = "Welcome to AIP",
142
+ *,
143
+ console: Console | None = None,
144
+ ) -> None:
122
145
  """Display a welcome panel with branding.
123
146
 
124
147
  Args:
125
148
  title: Custom title for the welcome panel
149
+ console: Optional console instance to print to. If None, uses self.console
126
150
  """
127
151
  banner = self.get_welcome_banner()
128
152
  panel = AIPPanel(
@@ -131,7 +155,8 @@ GDP Labs AI Agents Package
131
155
  border_style=BORDER,
132
156
  padding=(1, 2),
133
157
  )
134
- self.console.print(panel)
158
+ target_console = console or self.console
159
+ target_console.print(panel)
135
160
 
136
161
  def display_version_panel(self) -> None:
137
162
  """Display a panel with comprehensive version information."""
glaip_sdk/cli/auth.py CHANGED
@@ -3,6 +3,10 @@
3
3
  This module provides utilities for preparing authentication data for export,
4
4
  including interactive secret capture and placeholder generation.
5
5
 
6
+ These helpers are distinct from the AIP CLI's own authentication, which always
7
+ relies on the API URL and API key managed via ``aip configure`` / `AIP_API_*`
8
+ environment variables.
9
+
6
10
  Authors:
7
11
  Raymond Christopher (raymond.christopher@gdplabs.id)
8
12
  """
@@ -13,6 +17,9 @@ from typing import Any
13
17
  import click
14
18
  from rich.console import Console
15
19
 
20
+ from glaip_sdk.branding import HINT_PREFIX_STYLE, WARNING_STYLE
21
+ from glaip_sdk.cli.utils import command_hint, format_command_hint
22
+
16
23
 
17
24
  def prepare_authentication_export(
18
25
  auth: dict[str, Any] | None,
@@ -92,21 +99,19 @@ def _get_token_value(
92
99
  The token string, either provided by the user or the placeholder.
93
100
  """
94
101
  if prompt_for_secrets:
95
- console.print(
96
- "[yellow]Bearer token is missing or redacted. "
97
- "Please provide the token.[/yellow]"
98
- )
99
- token_value = click.prompt(
100
- "Bearer token (leave blank for placeholder)",
101
- default="",
102
- show_default=False,
102
+ return _prompt_secret_with_placeholder(
103
+ console,
104
+ warning_message="Bearer token is missing or redacted. Please provide the token.",
105
+ prompt_message="Bearer token (leave blank for placeholder)",
106
+ placeholder=placeholder,
107
+ tip_cli_command="configure",
108
+ tip_slash_command="configure",
103
109
  )
104
- return token_value.strip() or placeholder
105
110
 
106
111
  if not click.get_text_stream("stdin").isatty():
107
112
  console.print(
108
- "[yellow]⚠️ Non-interactive mode: "
109
- "using placeholder for bearer token[/yellow]"
113
+ f"[{WARNING_STYLE}]⚠️ Non-interactive mode: "
114
+ "using placeholder for bearer token[/]"
110
115
  )
111
116
  return placeholder
112
117
 
@@ -205,20 +210,19 @@ def _get_api_key_value(
205
210
  The API key value, either provided by the user or the placeholder.
206
211
  """
207
212
  if prompt_for_secrets:
208
- console.print(
209
- f"[yellow]API key value for '{key_name}' is missing or redacted.[/yellow]"
210
- )
211
- key_value = click.prompt(
212
- f"API key value for '{key_name}' (leave blank for placeholder)",
213
- default="",
214
- show_default=False,
213
+ return _prompt_secret_with_placeholder(
214
+ console,
215
+ warning_message=f"API key value for '{key_name}' is missing or redacted.",
216
+ prompt_message=f"API key value for '{key_name}' (leave blank for placeholder)",
217
+ placeholder=placeholder,
218
+ tip_cli_command="configure api-key",
219
+ tip_slash_command="configure",
215
220
  )
216
- return key_value.strip() or placeholder
217
221
 
218
222
  if not click.get_text_stream("stdin").isatty():
219
223
  console.print(
220
- f"[yellow]⚠️ Non-interactive mode: "
221
- f"using placeholder for API key '{key_name}'[/yellow]"
224
+ f"[{WARNING_STYLE}]⚠️ Non-interactive mode: "
225
+ f"using placeholder for API key '{key_name}'[/]"
222
226
  )
223
227
  return placeholder
224
228
 
@@ -362,17 +366,18 @@ def _prompt_or_placeholder(
362
366
  The provided value or the placeholder.
363
367
  """
364
368
  if prompt_for_secrets:
365
- console.print(f"[yellow]Header '{name}' is missing or redacted.[/yellow]")
366
- value = click.prompt(
367
- f"Value for header '{name}' (leave blank for placeholder)",
368
- default="",
369
- show_default=False,
369
+ return _prompt_secret_with_placeholder(
370
+ console,
371
+ warning_message=f"Header '{name}' is missing or redacted.",
372
+ prompt_message=f"Value for header '{name}' (leave blank for placeholder)",
373
+ placeholder=placeholder,
374
+ tip_cli_command="configure",
375
+ tip_slash_command="configure",
370
376
  )
371
- return value.strip() or placeholder
372
377
 
373
378
  if not click.get_text_stream("stdin").isatty():
374
379
  console.print(
375
- f"[yellow]⚠️ Non-interactive mode: using placeholder for header '{name}'[/yellow]"
380
+ f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for header '{name}'[/]"
376
381
  )
377
382
  return placeholder
378
383
 
@@ -412,3 +417,63 @@ def _build_custom_headers(
412
417
  )
413
418
 
414
419
  return headers
420
+
421
+
422
+ def _prompt_secret_with_placeholder(
423
+ console: Console,
424
+ *,
425
+ warning_message: str,
426
+ prompt_message: str,
427
+ placeholder: str,
428
+ tip_cli_command: str | None = "configure",
429
+ tip_slash_command: str | None = "configure",
430
+ mask_input: bool = True,
431
+ retry_limit: int = 1,
432
+ ) -> str:
433
+ """Prompt for a secret value with masking, retries, and placeholder fallback.
434
+
435
+ Args:
436
+ console: Rich console used to render messaging.
437
+ warning_message: Message shown before prompting (rendered with warning style).
438
+ prompt_message: The message passed to :func:`click.prompt`.
439
+ placeholder: Placeholder value inserted when the user skips input.
440
+ tip_cli_command: CLI command (without ``aip`` prefix) used to build hints.
441
+ tip_slash_command: Slash command counterpart used in hints.
442
+ mask_input: Whether to hide user input while typing.
443
+ retry_limit: Number of additional attempts when the user submits empty input.
444
+
445
+ Returns:
446
+ The value entered by the user or the provided placeholder.
447
+ """
448
+ console.print(f"[{WARNING_STYLE}]{warning_message}[/]")
449
+
450
+ tip = command_hint(tip_cli_command, tip_slash_command)
451
+ if tip:
452
+ console.print(
453
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] use {format_command_hint(tip) or tip} later if you want to update these credentials."
454
+ )
455
+
456
+ attempts = 0
457
+ while attempts <= retry_limit:
458
+ response = click.prompt(
459
+ prompt_message,
460
+ default="",
461
+ show_default=False,
462
+ hide_input=mask_input,
463
+ )
464
+ value = response.strip()
465
+ if value:
466
+ return value
467
+
468
+ if attempts < retry_limit:
469
+ console.print(
470
+ f"[{WARNING_STYLE}]No value entered. Enter a value or press Enter again to use the placeholder.[/]"
471
+ )
472
+ attempts += 1
473
+ continue
474
+
475
+ console.print("[dim]Using placeholder value.[/dim]")
476
+ return placeholder
477
+
478
+ # This line is unreachable as the loop always returns
479
+ # return placeholder
@@ -1,5 +1,5 @@
1
1
  """CLI commands package exports."""
2
2
 
3
- from glaip_sdk.cli.commands import agents, configure, mcps, models, tools
3
+ from glaip_sdk.cli.commands import agents, configure, mcps, models, tools, update
4
4
 
5
- __all__ = ["agents", "configure", "mcps", "models", "tools"]
5
+ __all__ = ["agents", "configure", "mcps", "models", "tools", "update"]
@@ -4,6 +4,8 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import json
8
10
  import os
9
11
  from collections.abc import Mapping
@@ -13,6 +15,14 @@ from typing import Any
13
15
  import click
14
16
  from rich.console import Console
15
17
 
18
+ from glaip_sdk.branding import (
19
+ ACCENT_STYLE,
20
+ ERROR_STYLE,
21
+ INFO,
22
+ SUCCESS,
23
+ SUCCESS_STYLE,
24
+ WARNING_STYLE,
25
+ )
16
26
  from glaip_sdk.cli.agent_config import (
17
27
  merge_agent_config_with_cli_args as merge_import_with_cli_args,
18
28
  )
@@ -45,6 +55,10 @@ from glaip_sdk.cli.io import (
45
55
  )
46
56
  from glaip_sdk.cli.resolution import resolve_resource_reference
47
57
  from glaip_sdk.cli.rich_helpers import markup_text, print_markup
58
+ from glaip_sdk.cli.transcript import (
59
+ maybe_launch_post_run_viewer,
60
+ store_transcript_for_session,
61
+ )
48
62
  from glaip_sdk.cli.utils import (
49
63
  _fuzzy_pick_for_resources,
50
64
  build_renderer,
@@ -65,9 +79,11 @@ from glaip_sdk.cli.validators import (
65
79
  )
66
80
  from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
67
81
  from glaip_sdk.exceptions import AgentTimeoutError
82
+ from glaip_sdk.icons import ICON_AGENT
68
83
  from glaip_sdk.utils import format_datetime, is_uuid
69
84
  from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
70
85
  from glaip_sdk.utils.import_export import convert_export_to_import_format
86
+ from glaip_sdk.utils.rendering.renderer.toggle import TranscriptToggleController
71
87
  from glaip_sdk.utils.validation import coerce_timeout
72
88
 
73
89
  console = Console()
@@ -168,17 +184,41 @@ def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
168
184
  return agent
169
185
 
170
186
 
187
+ def _normalise_model_name(value: Any) -> str | None:
188
+ """Return a cleaned model name or None when not usable."""
189
+ if value is None:
190
+ return None
191
+ if isinstance(value, str):
192
+ cleaned = value.strip()
193
+ return cleaned or None
194
+ if isinstance(value, bool):
195
+ return None
196
+ return str(value)
197
+
198
+
199
+ def _model_from_config(agent: Any) -> str | None:
200
+ """Extract a usable model name from an agent's configuration mapping."""
201
+ config = getattr(agent, "agent_config", None)
202
+ if not config or not isinstance(config, dict):
203
+ return None
204
+
205
+ for key in ("lm_name", "model"):
206
+ normalised = _normalise_model_name(config.get(key))
207
+ if normalised:
208
+ return normalised
209
+ return None
210
+
211
+
171
212
  def _get_agent_model_name(agent: Any) -> str | None:
172
213
  """Extract model name from agent configuration."""
173
- # Try different possible locations for model name
174
- if hasattr(agent, "agent_config") and agent.agent_config:
175
- if isinstance(agent.agent_config, dict):
176
- return agent.agent_config.get("lm_name") or agent.agent_config.get("model")
214
+ config_model = _model_from_config(agent)
215
+ if config_model:
216
+ return config_model
177
217
 
178
- if hasattr(agent, "model") and agent.model:
179
- return agent.model
218
+ normalised_attr = _normalise_model_name(getattr(agent, "model", None))
219
+ if normalised_attr:
220
+ return normalised_attr
180
221
 
181
- # Default fallback
182
222
  return DEFAULT_MODEL
183
223
 
184
224
 
@@ -198,7 +238,7 @@ def _resolve_resources_by_name(
198
238
  List of resolved resource IDs
199
239
  """
200
240
  out = []
201
- for ref in list(items or ()):
241
+ for ref in items or ():
202
242
  if is_uuid(ref):
203
243
  out.append(ref)
204
244
  continue
@@ -275,7 +315,7 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
275
315
  def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
276
316
  """Display full agent details using raw API data to preserve ALL fields."""
277
317
  if agent is None:
278
- handle_rich_output(ctx, markup_text("[red]❌ No agent provided[/red]"))
318
+ handle_rich_output(ctx, markup_text(f"[{ERROR_STYLE}]❌ No agent provided[/]"))
279
319
  return
280
320
 
281
321
  # Try to fetch and format raw agent data first
@@ -288,7 +328,7 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
288
328
 
289
329
  if formatted_data:
290
330
  # Use raw API data - this preserves ALL fields including account_id
291
- panel_title = f"🤖 {formatted_data.get('name', 'Unknown')}"
331
+ panel_title = f"{ICON_AGENT} {formatted_data.get('name', 'Unknown')}"
292
332
  output_result(
293
333
  ctx,
294
334
  formatted_data,
@@ -298,7 +338,7 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
298
338
  # Fall back to Pydantic model data if raw fetch fails
299
339
  handle_rich_output(
300
340
  ctx,
301
- markup_text("[yellow]Falling back to Pydantic model data[/yellow]"),
341
+ markup_text(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]"),
302
342
  )
303
343
 
304
344
  with spinner_context(
@@ -401,10 +441,10 @@ def list_agents(
401
441
  # Define table columns: (data_key, header, style, width)
402
442
  columns = [
403
443
  ("id", "ID", "dim", 36),
404
- ("name", "Name", "cyan", None),
405
- ("type", "Type", "yellow", None),
406
- ("framework", "Framework", "blue", None),
407
- ("version", "Version", "green", None),
444
+ ("name", "Name", ACCENT_STYLE, None),
445
+ ("type", "Type", WARNING_STYLE, None),
446
+ ("framework", "Framework", INFO, None),
447
+ ("version", "Version", SUCCESS, None),
408
448
  ]
409
449
 
410
450
  # Transform function for safe attribute access
@@ -439,7 +479,7 @@ def list_agents(
439
479
  output_list(
440
480
  ctx,
441
481
  agents,
442
- "🤖 Available Agents",
482
+ f"{ICON_AGENT} Available Agents",
443
483
  columns,
444
484
  transform_agent,
445
485
  skip_picker=simple
@@ -497,19 +537,21 @@ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> Non
497
537
  handle_rich_output(
498
538
  ctx,
499
539
  markup_text(
500
- f"[yellow]⚠️ Could not fetch full agent details: {e}[/yellow]"
540
+ f"[{WARNING_STYLE}]⚠️ Could not fetch full agent details: {e}[/]"
501
541
  ),
502
542
  )
503
543
  handle_rich_output(
504
544
  ctx,
505
- markup_text("[yellow]⚠️ Proceeding with available data[/yellow]"),
545
+ markup_text(
546
+ f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]"
547
+ ),
506
548
  )
507
549
 
508
550
  export_resource_to_file(agent, export_path, detected_format)
509
551
  handle_rich_output(
510
552
  ctx,
511
553
  markup_text(
512
- f"[green]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/green]"
554
+ f"[{SUCCESS_STYLE}]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/]"
513
555
  ),
514
556
  )
515
557
 
@@ -557,6 +599,23 @@ def _setup_run_renderer(ctx: Any, save: str | None, verbose: bool) -> Any:
557
599
  )
558
600
 
559
601
 
602
+ def _maybe_attach_transcript_toggle(ctx: Any, renderer: Any) -> None:
603
+ """Attach transcript toggle controller when interactive TTY is available."""
604
+ if renderer is None:
605
+ return
606
+
607
+ console_obj = getattr(renderer, "console", None)
608
+ if console_obj is None or not getattr(console_obj, "is_terminal", False):
609
+ return
610
+
611
+ tty_enabled = bool(get_ctx_value(ctx, "tty", True))
612
+ if not tty_enabled:
613
+ return
614
+
615
+ controller = TranscriptToggleController(enabled=True)
616
+ renderer.transcript_controller = controller
617
+
618
+
560
619
  def _prepare_run_kwargs(
561
620
  agent: Any,
562
621
  final_input_text: str,
@@ -621,7 +680,9 @@ def _save_run_transcript(save: str | None, result: Any, working_console: Any) ->
621
680
 
622
681
  with open(save, "w", encoding="utf-8") as f:
623
682
  f.write(content)
624
- print_markup(f"[green]Full debug output saved to: {save}[/green]", console=console)
683
+ print_markup(
684
+ f"[{SUCCESS_STYLE}]Full debug output saved to: {save}[/]", console=console
685
+ )
625
686
 
626
687
 
627
688
  @agents_group.command()
@@ -678,6 +739,10 @@ def run(
678
739
  """
679
740
  final_input_text = _validate_run_input(input_option, input_text)
680
741
 
742
+ if verbose:
743
+ _emit_verbose_guidance(ctx)
744
+ return
745
+
681
746
  try:
682
747
  client = get_client(ctx)
683
748
  agent = _resolve_agent(
@@ -686,6 +751,7 @@ def run(
686
751
 
687
752
  parsed_chat_history = _parse_chat_history(chat_history)
688
753
  renderer, working_console = _setup_run_renderer(ctx, save, verbose)
754
+ _maybe_attach_transcript_toggle(ctx, renderer)
689
755
 
690
756
  try:
691
757
  client.timeout = float(timeout)
@@ -703,8 +769,29 @@ def run(
703
769
 
704
770
  result = client.agents.run_agent(**run_kwargs, timeout=timeout)
705
771
 
772
+ slash_mode = _running_in_slash_mode(ctx)
773
+ agent_id = str(_safe_agent_attribute(agent, "id") or "") or None
774
+ agent_name = _safe_agent_attribute(agent, "name")
775
+ model_hint = _get_agent_model_name(agent)
776
+
777
+ transcript_context = store_transcript_for_session(
778
+ ctx,
779
+ renderer,
780
+ final_result=result,
781
+ agent_id=agent_id,
782
+ agent_name=agent_name,
783
+ model=model_hint,
784
+ source="slash" if slash_mode else "cli",
785
+ )
786
+
706
787
  _handle_run_output(ctx, result, renderer)
707
788
  _save_run_transcript(save, result, working_console)
789
+ maybe_launch_post_run_viewer(
790
+ ctx,
791
+ transcript_context,
792
+ console=console,
793
+ slash_mode=slash_mode,
794
+ )
708
795
 
709
796
  except AgentTimeoutError as e:
710
797
  error_msg = str(e)
@@ -714,6 +801,25 @@ def run(
714
801
  _handle_command_exception(ctx, e)
715
802
 
716
803
 
804
+ def _running_in_slash_mode(ctx: Any) -> bool:
805
+ ctx_obj = getattr(ctx, "obj", None)
806
+ return isinstance(ctx_obj, dict) and bool(ctx_obj.get("_slash_session"))
807
+
808
+
809
+ def _emit_verbose_guidance(ctx: Any) -> None:
810
+ if _running_in_slash_mode(ctx):
811
+ message = (
812
+ "[dim]Tip:[/] Verbose streaming has been retired in the command palette. Run the agent normally and open "
813
+ "the post-run viewer (Ctrl+T) to inspect the transcript."
814
+ )
815
+ else:
816
+ message = (
817
+ "[dim]Tip:[/] `--verbose` is no longer supported. Re-run without the flag and toggle the post-run viewer "
818
+ "(Ctrl+T) for detailed output."
819
+ )
820
+ handle_rich_output(ctx, markup_text(message))
821
+
822
+
717
823
  def _handle_import_file_logic(
718
824
  import_file: str,
719
825
  model: str | None,
@@ -1317,7 +1423,7 @@ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1317
1423
  handle_rich_output(
1318
1424
  ctx,
1319
1425
  markup_text(
1320
- f"[green]✅ Successfully synced {success_count} LangFlow agents ({total_count} total processed)[/green]"
1426
+ f"[{SUCCESS_STYLE}]✅ Successfully synced {success_count} LangFlow agents ({total_count} total processed)[/]"
1321
1427
  ),
1322
1428
  )
1323
1429