glaip-sdk 0.1.0__py3-none-any.whl → 0.1.2__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 (63) hide show
  1. glaip_sdk/_version.py +1 -3
  2. glaip_sdk/branding.py +2 -6
  3. glaip_sdk/cli/agent_config.py +2 -6
  4. glaip_sdk/cli/auth.py +11 -30
  5. glaip_sdk/cli/commands/agents.py +45 -107
  6. glaip_sdk/cli/commands/configure.py +12 -36
  7. glaip_sdk/cli/commands/mcps.py +26 -63
  8. glaip_sdk/cli/commands/models.py +2 -4
  9. glaip_sdk/cli/commands/tools.py +22 -35
  10. glaip_sdk/cli/commands/update.py +3 -8
  11. glaip_sdk/cli/config.py +1 -3
  12. glaip_sdk/cli/display.py +4 -12
  13. glaip_sdk/cli/io.py +8 -14
  14. glaip_sdk/cli/main.py +10 -30
  15. glaip_sdk/cli/mcp_validators.py +5 -15
  16. glaip_sdk/cli/pager.py +3 -9
  17. glaip_sdk/cli/parsers/json_input.py +11 -22
  18. glaip_sdk/cli/resolution.py +3 -9
  19. glaip_sdk/cli/rich_helpers.py +1 -3
  20. glaip_sdk/cli/slash/agent_session.py +5 -10
  21. glaip_sdk/cli/slash/prompt.py +3 -10
  22. glaip_sdk/cli/slash/session.py +46 -95
  23. glaip_sdk/cli/transcript/cache.py +6 -19
  24. glaip_sdk/cli/transcript/capture.py +6 -20
  25. glaip_sdk/cli/transcript/launcher.py +1 -3
  26. glaip_sdk/cli/transcript/viewer.py +11 -40
  27. glaip_sdk/cli/update_notifier.py +165 -21
  28. glaip_sdk/cli/utils.py +33 -84
  29. glaip_sdk/cli/validators.py +11 -12
  30. glaip_sdk/client/_agent_payloads.py +10 -30
  31. glaip_sdk/client/agents.py +33 -63
  32. glaip_sdk/client/base.py +77 -35
  33. glaip_sdk/client/mcps.py +1 -3
  34. glaip_sdk/client/run_rendering.py +6 -14
  35. glaip_sdk/client/tools.py +8 -24
  36. glaip_sdk/client/validators.py +20 -48
  37. glaip_sdk/exceptions.py +1 -3
  38. glaip_sdk/models.py +14 -33
  39. glaip_sdk/payload_schemas/agent.py +1 -3
  40. glaip_sdk/utils/agent_config.py +4 -14
  41. glaip_sdk/utils/client_utils.py +7 -21
  42. glaip_sdk/utils/display.py +2 -6
  43. glaip_sdk/utils/general.py +1 -3
  44. glaip_sdk/utils/import_export.py +3 -9
  45. glaip_sdk/utils/rendering/formatting.py +2 -5
  46. glaip_sdk/utils/rendering/models.py +2 -6
  47. glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
  48. glaip_sdk/utils/rendering/renderer/base.py +63 -189
  49. glaip_sdk/utils/rendering/renderer/debug.py +4 -14
  50. glaip_sdk/utils/rendering/renderer/panels.py +1 -3
  51. glaip_sdk/utils/rendering/renderer/progress.py +3 -11
  52. glaip_sdk/utils/rendering/renderer/stream.py +7 -19
  53. glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
  54. glaip_sdk/utils/rendering/step_tree_state.py +1 -3
  55. glaip_sdk/utils/rendering/steps.py +29 -83
  56. glaip_sdk/utils/resource_refs.py +4 -13
  57. glaip_sdk/utils/serialization.py +14 -46
  58. glaip_sdk/utils/validation.py +4 -4
  59. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/METADATA +1 -1
  60. glaip_sdk-0.1.2.dist-info/RECORD +82 -0
  61. glaip_sdk-0.1.0.dist-info/RECORD +0 -82
  62. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/WHEEL +0 -0
  63. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -79,9 +79,7 @@ def validate_mcp_config_structure(
79
79
  requirement = "Missing required 'url' field with a non-empty string value."
80
80
  if transport:
81
81
  requirement += f" Required for transport '{transport}'."
82
- raise click.ClickException(
83
- format_validation_error(f"Invalid {source} value", requirement)
84
- )
82
+ raise click.ClickException(format_validation_error(f"Invalid {source} value", requirement))
85
83
 
86
84
  parsed_url = urlparse(url_value)
87
85
  if parsed_url.scheme not in {"http", "https"} or not parsed_url.netloc:
@@ -95,9 +93,7 @@ def validate_mcp_config_structure(
95
93
  return config
96
94
 
97
95
 
98
- def _validate_headers_mapping(
99
- headers: Any, *, source: str, context: str
100
- ) -> dict[str, str]:
96
+ def _validate_headers_mapping(headers: Any, *, source: str, context: str) -> dict[str, str]:
101
97
  """Validate headers mapping for authentication.
102
98
 
103
99
  Args:
@@ -156,9 +152,7 @@ def _validate_bearer_token_auth(auth: dict[str, Any], source: str) -> dict[str,
156
152
  if isinstance(token, str) and token.strip():
157
153
  return {"type": "bearer-token", "token": token}
158
154
  headers = auth.get("headers")
159
- normalized_headers = _validate_headers_mapping(
160
- headers, source=source, context="bearer-token authentication"
161
- )
155
+ normalized_headers = _validate_headers_mapping(headers, source=source, context="bearer-token authentication")
162
156
  return {"type": "bearer-token", "headers": normalized_headers}
163
157
 
164
158
 
@@ -177,9 +171,7 @@ def _validate_api_key_auth(auth: dict[str, Any], source: str) -> dict[str, Any]:
177
171
  """
178
172
  headers = auth.get("headers")
179
173
  if headers is not None:
180
- normalized_headers = _validate_headers_mapping(
181
- headers, source=source, context="api-key authentication"
182
- )
174
+ normalized_headers = _validate_headers_mapping(headers, source=source, context="api-key authentication")
183
175
  return {"type": "api-key", "headers": normalized_headers}
184
176
 
185
177
  key = auth.get("key")
@@ -215,9 +207,7 @@ def _validate_custom_header_auth(auth: dict[str, Any], source: str) -> dict[str,
215
207
  click.ClickException: If custom-header structure is invalid
216
208
  """
217
209
  headers = auth.get("headers")
218
- normalized_headers = _validate_headers_mapping(
219
- headers, source=source, context="custom-header authentication"
220
- )
210
+ normalized_headers = _validate_headers_mapping(headers, source=source, context="custom-header authentication")
221
211
  return {"type": "custom-header", "headers": normalized_headers}
222
212
 
223
213
 
glaip_sdk/cli/pager.py CHANGED
@@ -156,9 +156,7 @@ def _resolve_pager_command() -> tuple[list[str] | None, str | None]:
156
156
  return pager_cmd, less_path
157
157
 
158
158
 
159
- def _run_less_pager(
160
- pager_cmd: list[str] | None, less_path: str | None, tmp_path: str
161
- ) -> None:
159
+ def _run_less_pager(pager_cmd: list[str] | None, less_path: str | None, tmp_path: str) -> None:
162
160
  """Run less pager with appropriate command and flags.
163
161
 
164
162
  Args:
@@ -195,9 +193,7 @@ def _run_more_pager(tmp_path: str) -> None:
195
193
  raise FileNotFoundError("more command not found")
196
194
 
197
195
 
198
- def _run_pager_with_temp_file(
199
- pager_runner: Callable[[str], None], ansi_text: str
200
- ) -> bool:
196
+ def _run_pager_with_temp_file(pager_runner: Callable[[str], None], ansi_text: str) -> bool:
201
197
  """Run a pager using a temporary file containing the content.
202
198
 
203
199
  Args:
@@ -239,9 +235,7 @@ def _page_with_system_pager(ansi_text: str) -> bool:
239
235
  pager_cmd, less_path = _resolve_pager_command()
240
236
 
241
237
  if pager_cmd or less_path:
242
- return _run_pager_with_temp_file(
243
- lambda tmp_path: _run_less_pager(pager_cmd, less_path, tmp_path), ansi_text
244
- )
238
+ return _run_pager_with_temp_file(lambda tmp_path: _run_less_pager(pager_cmd, less_path, tmp_path), ansi_text)
245
239
 
246
240
  if platform.system().lower().startswith("win"):
247
241
  return False
@@ -33,9 +33,7 @@ def _looks_like_file_path(value: str) -> bool:
33
33
  )
34
34
 
35
35
 
36
- def _format_file_error(
37
- prefix: str, file_path_str: str, resolved_path: Path, *, detail: str | None = None
38
- ) -> str:
36
+ def _format_file_error(prefix: str, file_path_str: str, resolved_path: Path, *, detail: str | None = None) -> str:
39
37
  r"""Format a file-related error message with path context.
40
38
 
41
39
  Args:
@@ -77,16 +75,12 @@ def _parse_json_from_file(file_path_str: str) -> Any:
77
75
 
78
76
  # Check if file exists and is a regular file
79
77
  if not file_path.is_file():
80
- raise click.ClickException(
81
- _format_file_error("File not found or not a file", file_path_str, file_path)
82
- )
78
+ raise click.ClickException(_format_file_error("File not found or not a file", file_path_str, file_path))
83
79
 
84
80
  # Check if file is readable
85
81
  if not os.access(file_path, os.R_OK):
86
82
  raise click.ClickException(
87
- _format_file_error(
88
- "File not readable (permission denied)", file_path_str, file_path
89
- )
83
+ _format_file_error("File not readable (permission denied)", file_path_str, file_path)
90
84
  )
91
85
 
92
86
  # Read file content
@@ -94,16 +88,12 @@ def _parse_json_from_file(file_path_str: str) -> Any:
94
88
  content = file_path.read_text(encoding="utf-8")
95
89
  except Exception as e:
96
90
  raise click.ClickException(
97
- _format_file_error(
98
- "Error reading file", file_path_str, file_path, detail=f"Error: {e}"
99
- )
100
- )
91
+ _format_file_error("Error reading file", file_path_str, file_path, detail=f"Error: {e}")
92
+ ) from e
101
93
 
102
94
  # Check for empty content
103
95
  if not content.strip():
104
- raise click.ClickException(
105
- _format_file_error("File is empty", file_path_str, file_path)
106
- )
96
+ raise click.ClickException(_format_file_error("File is empty", file_path_str, file_path))
107
97
 
108
98
  # Determine file format and parse accordingly
109
99
  file_ext = file_path.suffix.lower()
@@ -120,7 +110,7 @@ def _parse_json_from_file(file_path_str: str) -> Any:
120
110
  file_path,
121
111
  detail=f"Error: {e}",
122
112
  )
123
- )
113
+ ) from e
124
114
  else:
125
115
  # Default to JSON parsing
126
116
  try:
@@ -133,7 +123,7 @@ def _parse_json_from_file(file_path_str: str) -> Any:
133
123
  file_path,
134
124
  detail=f"Error: {e.msg} at line {e.lineno}, column {e.colno}",
135
125
  )
136
- )
126
+ ) from e
137
127
 
138
128
 
139
129
  def parse_json_input(value: str | None) -> Any:
@@ -180,9 +170,8 @@ def parse_json_input(value: str | None) -> Any:
180
170
  f"Error: {e.msg} at line {e.lineno}, column {e.colno}\n"
181
171
  f"\n💡 Did you mean to load this from a file? "
182
172
  f"File-based config values should start with @ (e.g., @{trimmed})"
183
- )
173
+ ) from e
184
174
 
185
175
  raise click.ClickException(
186
- f"Invalid JSON in inline value\n"
187
- f"Error: {e.msg} at line {e.lineno}, column {e.colno}"
188
- )
176
+ f"Invalid JSON in inline value\nError: {e.msg} at line {e.lineno}, column {e.colno}"
177
+ ) from e
@@ -51,14 +51,8 @@ def resolve_resource_reference(
51
51
  click.ClickException: If resolution fails.
52
52
  """
53
53
  try:
54
- message = (
55
- spinner_message
56
- if spinner_message is not None
57
- else f"[bold blue]Fetching {label}…[/bold blue]"
58
- )
59
- with spinner_context(
60
- ctx, message, spinner_style=ACCENT_STYLE
61
- ) as status_indicator:
54
+ message = spinner_message if spinner_message is not None else f"[bold blue]Fetching {label}…[/bold blue]"
55
+ with spinner_context(ctx, message, spinner_style=ACCENT_STYLE) as status_indicator:
62
56
  return resolve_resource(
63
57
  ctx,
64
58
  reference,
@@ -70,4 +64,4 @@ def resolve_resource_reference(
70
64
  status_indicator=status_indicator,
71
65
  )
72
66
  except Exception as e:
73
- raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}")
67
+ raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}") from e
@@ -21,9 +21,7 @@ def markup_text(message: str, **kwargs: Any) -> Text:
21
21
  return Text(message, **kwargs)
22
22
 
23
23
 
24
- def print_markup(
25
- message: str, *, console: Console | None = None, **kwargs: Any
26
- ) -> None:
24
+ def print_markup(message: str, *, console: Console | None = None, **kwargs: Any) -> None:
27
25
  """Print markup-aware text to the provided console (default: new Console)."""
28
26
  target_console = console or Console()
29
27
  target_console.print(markup_text(message, **kwargs))
@@ -46,9 +46,7 @@ class AgentRunSession:
46
46
 
47
47
  def run(self) -> None:
48
48
  """Run the interactive agent session loop."""
49
- self.session.set_contextual_commands(
50
- self._contextual_completion_help, include_global=False
51
- )
49
+ self.session.set_contextual_commands(self._contextual_completion_help, include_global=False)
52
50
  previous_agent = getattr(self.session, "_current_agent", None)
53
51
  self.session._current_agent = self.agent
54
52
  clear_ready = getattr(self.session, "clear_agent_transcript_ready", None)
@@ -157,7 +155,8 @@ class AgentRunSession:
157
155
  try:
158
156
  self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
159
157
  self.console.print(
160
- f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use {format_command_hint('/help') or '/help'} for shortcuts."
158
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use "
159
+ f"{format_command_hint('/help') or '/help'} for shortcuts."
161
160
  )
162
161
  except click.ClickException as exc:
163
162
  self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
@@ -173,9 +172,7 @@ class AgentRunSession:
173
172
  mark_ready(self._agent_id, run_id)
174
173
  if self._open_transcript_viewer():
175
174
  return
176
- self.console.print(
177
- "[dim]Transcript viewer is unavailable in this environment.[/dim]"
178
- )
175
+ self.console.print("[dim]Transcript viewer is unavailable in this environment.[/dim]")
179
176
 
180
177
  def _transcript_matches(self, payload: Any, manifest: Any) -> bool:
181
178
  """Return True when the latest transcript belongs to this agent."""
@@ -197,9 +194,7 @@ class AgentRunSession:
197
194
  self.session.console.clear()
198
195
  except Exception: # pragma: no cover - defensive cleanup
199
196
  pass
200
- if (
201
- current_agent is not None
202
- ): # pragma: no cover - UI refresh best effort
197
+ if current_agent is not None: # pragma: no cover - UI refresh best effort
203
198
  try:
204
199
  self.session._render_header(current_agent, focus_agent=True)
205
200
  except Exception: # pragma: no cover - defensive cleanup
@@ -178,15 +178,10 @@ def _iter_command_completions(
178
178
 
179
179
  def _should_include_commands(session: SlashSession) -> bool:
180
180
  """Check if commands should be included in completions."""
181
- return not (
182
- session.get_contextual_commands()
183
- and not session.should_include_global_commands()
184
- )
181
+ return not (session.get_contextual_commands() and not session.should_include_global_commands())
185
182
 
186
183
 
187
- def _generate_command_completions(
188
- cmd: Any, prefix: str, text: str, seen: set[str]
189
- ) -> Iterable[Completion]:
184
+ def _generate_command_completions(cmd: Any, prefix: str, text: str, seen: set[str]) -> Iterable[Completion]:
190
185
  """Generate completion items for a single command."""
191
186
  for alias in (cmd.name, *cmd.aliases):
192
187
  if alias in seen or alias.startswith("?"):
@@ -211,9 +206,7 @@ def _iter_contextual_completions(
211
206
  prefix = text[1:]
212
207
  seen: set[str] = set()
213
208
 
214
- contextual_commands = sorted(
215
- session.get_contextual_commands().items(), key=lambda item: item[0]
216
- )
209
+ contextual_commands = sorted(session.get_contextual_commands().items(), key=lambda item: item[0])
217
210
 
218
211
  for alias, help_text in contextual_commands:
219
212
  if alias in seen:
@@ -120,6 +120,16 @@ class SlashSession:
120
120
  # ------------------------------------------------------------------
121
121
  # Session orchestration
122
122
  # ------------------------------------------------------------------
123
+ def refresh_branding(self, sdk_version: str | None = None) -> None:
124
+ """Refresh branding assets after an in-session SDK upgrade."""
125
+ self._branding = AIPBranding.create_from_sdk(
126
+ sdk_version=sdk_version,
127
+ package_name="glaip-sdk",
128
+ )
129
+ self._welcome_rendered = False
130
+ self.console.print()
131
+ self.console.print(f"[{SUCCESS_STYLE}]CLI updated to {self._branding.version}. Refreshing banner...[/]")
132
+ self._render_header(initial=True)
123
133
 
124
134
  def _setup_prompt_toolkit(self) -> None:
125
135
  session, style = setup_prompt_toolkit(self, interactive=self._interactive)
@@ -181,16 +191,12 @@ class SlashSession:
181
191
  return True
182
192
 
183
193
  if not raw.startswith("/"):
184
- self.console.print(
185
- f"[{INFO_STYLE}]Hint:[/] start commands with `/`. Try `/agents` to select an agent."
186
- )
194
+ self.console.print(f"[{INFO_STYLE}]Hint:[/] start commands with `/`. Try `/agents` to select an agent.")
187
195
  return True
188
196
 
189
197
  return self.handle_command(raw)
190
198
 
191
- def _run_non_interactive(
192
- self, initial_commands: Iterable[str] | None = None
193
- ) -> None:
199
+ def _run_non_interactive(self, initial_commands: Iterable[str] | None = None) -> None:
194
200
  """Run slash commands in non-interactive mode."""
195
201
  commands = list(initial_commands or [])
196
202
  if not commands:
@@ -205,16 +211,12 @@ class SlashSession:
205
211
  def _ensure_configuration(self) -> bool:
206
212
  """Ensure the CLI has both API URL and credentials before continuing."""
207
213
  while not self._configuration_ready():
208
- self.console.print(
209
- f"[{WARNING_STYLE}]Configuration required.[/] Launching `/login` wizard..."
210
- )
214
+ self.console.print(f"[{WARNING_STYLE}]Configuration required.[/] Launching `/login` wizard...")
211
215
  self._suppress_login_layout = True
212
216
  try:
213
217
  self._cmd_login([], False)
214
218
  except KeyboardInterrupt:
215
- self.console.print(
216
- f"[{ERROR_STYLE}]Configuration aborted. Closing the command palette.[/]"
217
- )
219
+ self.console.print(f"[{ERROR_STYLE}]Configuration aborted. Closing the command palette.[/]")
218
220
  return False
219
221
  finally:
220
222
  self._suppress_login_layout = False
@@ -246,9 +248,7 @@ class SlashSession:
246
248
  if command is None:
247
249
  suggestion = self._suggest(verb)
248
250
  if suggestion:
249
- self.console.print(
250
- f"[{WARNING_STYLE}]Unknown command '{verb}'. Did you mean '/{suggestion}'?[/]"
251
- )
251
+ self.console.print(f"[{WARNING_STYLE}]Unknown command '{verb}'. Did you mean '/{suggestion}'?[/]")
252
252
  else:
253
253
  help_command = "/help"
254
254
  help_hint = format_command_hint(help_command) or help_command
@@ -291,9 +291,7 @@ class SlashSession:
291
291
 
292
292
  panel_items = [table]
293
293
  if self.last_run_input:
294
- panel_items.append(
295
- Text.from_markup(f"[dim]Last run input:[/] {self.last_run_input}")
296
- )
294
+ panel_items.append(Text.from_markup(f"[dim]Last run input:[/] {self.last_run_input}"))
297
295
  panel_items.append(
298
296
  Text.from_markup(
299
297
  "[dim]Global commands (e.g. `/login`, `/status`) remain available inside the agent prompt.[/dim]"
@@ -321,7 +319,9 @@ class SlashSession:
321
319
  table.add_row(verb, cmd.help)
322
320
 
323
321
  tip = Text.from_markup(
324
- f"[{HINT_PREFIX_STYLE}]Tip:[/] {format_command_hint(self.AGENTS_COMMAND) or self.AGENTS_COMMAND} lets you jump into an agent run prompt quickly."
322
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] "
323
+ f"{format_command_hint(self.AGENTS_COMMAND) or self.AGENTS_COMMAND} "
324
+ "lets you jump into an agent run prompt quickly."
325
325
  )
326
326
 
327
327
  self.console.print(
@@ -352,7 +352,7 @@ class SlashSession:
352
352
  previous_console = None
353
353
  try:
354
354
  status_module = importlib.import_module("glaip_sdk.cli.main")
355
- status_command = getattr(status_module, "status")
355
+ status_command = status_module.status
356
356
 
357
357
  if ctx_obj is not None:
358
358
  previous_console = ctx_obj.get("_slash_console")
@@ -360,9 +360,7 @@ class SlashSession:
360
360
 
361
361
  self.ctx.invoke(status_command)
362
362
 
363
- hints: list[tuple[str, str]] = [
364
- (self.AGENTS_COMMAND, "Browse agents and run them")
365
- ]
363
+ hints: list[tuple[str, str]] = [(self.AGENTS_COMMAND, "Browse agents and run them")]
366
364
  if self.recent_agents:
367
365
  top = self.recent_agents[0]
368
366
  label = top.get("name") or top.get("id")
@@ -417,9 +415,7 @@ class SlashSession:
417
415
  """Handle case when no agents are available."""
418
416
  hint = command_hint("agents create", slash_command=None, ctx=self.ctx)
419
417
  if hint:
420
- self.console.print(
421
- f"[{WARNING_STYLE}]No agents available. Use `{hint}` to add one.[/]"
422
- )
418
+ self.console.print(f"[{WARNING_STYLE}]No agents available. Use `{hint}` to add one.[/]")
423
419
  else:
424
420
  self.console.print(f"[{WARNING_STYLE}]No agents available.[/]")
425
421
 
@@ -542,17 +538,13 @@ class SlashSession:
542
538
  payload, manifest = self._get_last_transcript()
543
539
  if payload is None or manifest is None:
544
540
  if announce:
545
- self.console.print(
546
- f"[{WARNING_STYLE}]No transcript is available yet. Run an agent first.[/]"
547
- )
541
+ self.console.print(f"[{WARNING_STYLE}]No transcript is available yet. Run an agent first.[/]")
548
542
  return
549
543
 
550
544
  run_id = manifest.get("run_id")
551
545
  if not run_id:
552
546
  if announce:
553
- self.console.print(
554
- f"[{WARNING_STYLE}]Latest transcript is missing run metadata.[/]"
555
- )
547
+ self.console.print(f"[{WARNING_STYLE}]Latest transcript is missing run metadata.[/]")
556
548
  return
557
549
 
558
550
  viewer_ctx = ViewerContext(
@@ -571,9 +563,7 @@ class SlashSession:
571
563
  run_viewer_session(self.console, viewer_ctx, _export)
572
564
  except Exception as exc: # pragma: no cover - interactive failures
573
565
  if announce:
574
- self.console.print(
575
- f"[{ERROR_STYLE}]Failed to launch transcript viewer: {exc}[/]"
576
- )
566
+ self.console.print(f"[{ERROR_STYLE}]Failed to launch transcript viewer: {exc}[/]")
577
567
 
578
568
  def _get_last_transcript(self) -> tuple[Any | None, dict[str, Any] | None]:
579
569
  """Fetch the most recently stored transcript payload and manifest."""
@@ -593,12 +583,11 @@ class SlashSession:
593
583
  if manifest_entry is None:
594
584
  if run_id:
595
585
  self.console.print(
596
- f"[{WARNING_STYLE}]No cached transcript found with run id {run_id!r}. Omit the run id to export the most recent run.[/]"
586
+ f"[{WARNING_STYLE}]No cached transcript found with run id {run_id!r}. "
587
+ "Omit the run id to export the most recent run.[/]"
597
588
  )
598
589
  else:
599
- self.console.print(
600
- f"[{WARNING_STYLE}]No cached transcripts available yet. Run an agent first.[/]"
601
- )
590
+ self.console.print(f"[{WARNING_STYLE}]No cached transcripts available yet. Run an agent first.[/]")
602
591
  return False
603
592
 
604
593
  destination = self._resolve_export_destination(path_arg, manifest_entry)
@@ -620,9 +609,7 @@ class SlashSession:
620
609
  self.console.print(f"[{SUCCESS_STYLE}]Transcript exported to[/] {exported}")
621
610
  return True
622
611
 
623
- def _resolve_export_destination(
624
- self, path_arg: str | None, manifest_entry: dict[str, Any]
625
- ) -> Path | None:
612
+ def _resolve_export_destination(self, path_arg: str | None, manifest_entry: dict[str, Any]) -> Path | None:
626
613
  if path_arg:
627
614
  return normalise_export_destination(Path(path_arg))
628
615
 
@@ -640,9 +627,7 @@ class SlashSession:
640
627
  def _cmd_update(self, args: list[str], _invoked_from_agent: bool) -> bool:
641
628
  """Slash handler for `/update` command."""
642
629
  if args:
643
- self.console.print(
644
- "Usage: `/update` upgrades glaip-sdk to the latest published version."
645
- )
630
+ self.console.print("Usage: `/update` upgrades glaip-sdk to the latest published version.")
646
631
  return True
647
632
 
648
633
  try:
@@ -742,16 +727,12 @@ class SlashSession:
742
727
  prompt_kwargs: dict[str, Any] = {"style": self._ptk_style}
743
728
  if placeholder:
744
729
  placeholder_text = (
745
- FormattedText([("class:placeholder", placeholder)])
746
- if FormattedText is not None
747
- else placeholder
730
+ FormattedText([("class:placeholder", placeholder)]) if FormattedText is not None else placeholder
748
731
  )
749
732
  prompt_kwargs["placeholder"] = placeholder_text
750
733
  return prompt_kwargs
751
734
 
752
- def _prompt_with_prompt_toolkit(
753
- self, message: str | Callable[[], Any], placeholder: str | None
754
- ) -> str:
735
+ def _prompt_with_prompt_toolkit(self, message: str | Callable[[], Any], placeholder: str | None) -> str:
755
736
  """Handle prompting with prompt_toolkit."""
756
737
  with patch_stdout(): # pragma: no cover - UI specific
757
738
  if callable(message):
@@ -765,9 +746,7 @@ class SlashSession:
765
746
 
766
747
  try:
767
748
  return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
768
- except (
769
- TypeError
770
- ): # pragma: no cover - compatibility with older prompt_toolkit
749
+ except TypeError: # pragma: no cover - compatibility with older prompt_toolkit
771
750
  prompt_kwargs.pop("placeholder", None)
772
751
  return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
773
752
 
@@ -786,9 +765,7 @@ class SlashSession:
786
765
  except Exception:
787
766
  return str(raw_value)
788
767
 
789
- def _prompt_with_basic_input(
790
- self, message: str | Callable[[], Any], placeholder: str | None
791
- ) -> str:
768
+ def _prompt_with_basic_input(self, message: str | Callable[[], Any], placeholder: str | None) -> str:
792
769
  """Handle prompting with basic input."""
793
770
  if placeholder:
794
771
  self.console.print(f"[dim]{placeholder}[/dim]")
@@ -798,9 +775,7 @@ class SlashSession:
798
775
 
799
776
  return input(actual_message)
800
777
 
801
- def _prompt(
802
- self, message: str | Callable[[], Any], *, placeholder: str | None = None
803
- ) -> str:
778
+ def _prompt(self, message: str | Callable[[], Any], *, placeholder: str | None = None) -> str:
804
779
  """Main prompt function with reduced complexity."""
805
780
  if self._ptk_session and self._ptk_style and patch_stdout:
806
781
  return self._prompt_with_prompt_toolkit(message, placeholder)
@@ -812,9 +787,7 @@ class SlashSession:
812
787
  self._client = get_client(self.ctx)
813
788
  return self._client
814
789
 
815
- def set_contextual_commands(
816
- self, commands: dict[str, str] | None, *, include_global: bool = True
817
- ) -> None:
790
+ def set_contextual_commands(self, commands: dict[str, str] | None, *, include_global: bool = True) -> None:
818
791
  """Set context-specific commands that should appear in completions."""
819
792
  self._contextual_commands = dict(commands or {})
820
793
  self._contextual_include_global = include_global if commands else True
@@ -834,9 +807,7 @@ class SlashSession:
834
807
  "type": getattr(agent, "type", "") or "",
835
808
  }
836
809
 
837
- self.recent_agents = [
838
- a for a in self.recent_agents if a.get("id") != agent_data["id"]
839
- ]
810
+ self.recent_agents = [a for a in self.recent_agents if a.get("id") != agent_data["id"]]
840
811
  self.recent_agents.insert(0, agent_data)
841
812
  self.recent_agents = self.recent_agents[:5]
842
813
 
@@ -891,9 +862,7 @@ class SlashSession:
891
862
  keybar = self._build_keybar()
892
863
 
893
864
  header_grid.add_row(keybar, "")
894
- self.console.print(
895
- AIPPanel(header_grid, title="Agent Session", border_style=PRIMARY)
896
- )
865
+ self.console.print(AIPPanel(header_grid, title="Agent Session", border_style=PRIMARY))
897
866
 
898
867
  def _get_agent_info(self, active_agent: Any) -> dict[str, str]:
899
868
  """Extract agent information for display."""
@@ -914,9 +883,7 @@ class SlashSession:
914
883
  has_transcript = bool(payload and manifest and manifest.get("run_id"))
915
884
  run_id = (manifest or {}).get("run_id")
916
885
  transcript_ready = (
917
- has_transcript
918
- and latest_agent_id == agent_id
919
- and self._agent_transcript_ready.get(agent_id) == run_id
886
+ has_transcript and latest_agent_id == agent_id and self._agent_transcript_ready.get(agent_id) == run_id
920
887
  )
921
888
 
922
889
  return {
@@ -925,9 +892,7 @@ class SlashSession:
925
892
  "run_id": run_id,
926
893
  }
927
894
 
928
- def _build_header_grid(
929
- self, agent_info: dict[str, str], transcript_status: dict[str, Any]
930
- ) -> AIPGrid:
895
+ def _build_header_grid(self, agent_info: dict[str, str], transcript_status: dict[str, Any]) -> AIPGrid:
931
896
  """Build the main header grid with agent information."""
932
897
  header_grid = AIPGrid(expand=True)
933
898
  header_grid.add_column(ratio=3)
@@ -938,11 +903,7 @@ class SlashSession:
938
903
  f"[{ACCENT_STYLE}]{agent_info['id']}[/]"
939
904
  )
940
905
  status_line = f"[{SUCCESS_STYLE}]ready[/]"
941
- status_line += (
942
- " · transcript ready"
943
- if transcript_status["transcript_ready"]
944
- else " · transcript pending"
945
- )
906
+ status_line += " · transcript ready" if transcript_status["transcript_ready"] else " · transcript pending"
946
907
  header_grid.add_row(primary_line, status_line)
947
908
 
948
909
  if agent_info["description"]:
@@ -967,9 +928,7 @@ class SlashSession:
967
928
 
968
929
  return keybar
969
930
 
970
- def _render_main_header(
971
- self, active_agent: Any | None = None, *, full: bool = False
972
- ) -> None:
931
+ def _render_main_header(self, active_agent: Any | None = None, *, full: bool = False) -> None:
973
932
  """Render the main AIP environment header."""
974
933
  config = self._load_config()
975
934
 
@@ -987,7 +946,7 @@ class SlashSession:
987
946
  rendered_line = " ".join(segments)
988
947
 
989
948
  if full:
990
- self.console.print(rendered_line)
949
+ self.console.print(rendered_line, soft_wrap=False)
991
950
  return
992
951
 
993
952
  status_bar = AIPGrid(expand=True)
@@ -1060,9 +1019,7 @@ class SlashSession:
1060
1019
  title: str = "Quick actions",
1061
1020
  inline: bool = False,
1062
1021
  ) -> None:
1063
- hint_list = [
1064
- (command, description) for command, description in hints if command
1065
- ]
1022
+ hint_list = [(command, description) for command, description in hints if command]
1066
1023
  if not hint_list:
1067
1024
  return
1068
1025
 
@@ -1083,11 +1040,7 @@ class SlashSession:
1083
1040
  body_lines.append(Text.from_markup(formatted))
1084
1041
 
1085
1042
  panel_content = Group(*body_lines)
1086
- self.console.print(
1087
- AIPPanel(
1088
- panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False
1089
- )
1090
- )
1043
+ self.console.print(AIPPanel(panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False))
1091
1044
 
1092
1045
  def _load_config(self) -> dict[str, Any]:
1093
1046
  if self._config_cache is None:
@@ -1097,9 +1050,7 @@ class SlashSession:
1097
1050
  self._config_cache = {}
1098
1051
  return self._config_cache
1099
1052
 
1100
- def _resolve_agent_from_ref(
1101
- self, client: Any, available_agents: list[Any], ref: str
1102
- ) -> Any | None:
1053
+ def _resolve_agent_from_ref(self, client: Any, available_agents: list[Any], ref: str) -> Any | None:
1103
1054
  ref = ref.strip()
1104
1055
  if not ref:
1105
1056
  return None