glaip-sdk 0.6.19__py3-none-any.whl → 0.7.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. glaip_sdk/agents/base.py +283 -30
  2. glaip_sdk/agents/component.py +233 -0
  3. glaip_sdk/branding.py +113 -2
  4. glaip_sdk/cli/account_store.py +15 -0
  5. glaip_sdk/cli/auth.py +14 -8
  6. glaip_sdk/cli/commands/accounts.py +1 -1
  7. glaip_sdk/cli/commands/agents/__init__.py +116 -0
  8. glaip_sdk/cli/commands/agents/_common.py +562 -0
  9. glaip_sdk/cli/commands/agents/create.py +155 -0
  10. glaip_sdk/cli/commands/agents/delete.py +64 -0
  11. glaip_sdk/cli/commands/agents/get.py +89 -0
  12. glaip_sdk/cli/commands/agents/list.py +129 -0
  13. glaip_sdk/cli/commands/agents/run.py +264 -0
  14. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  15. glaip_sdk/cli/commands/agents/update.py +112 -0
  16. glaip_sdk/cli/commands/common_config.py +1 -1
  17. glaip_sdk/cli/commands/configure.py +1 -2
  18. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  19. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  20. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  21. glaip_sdk/cli/commands/mcps/create.py +152 -0
  22. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  23. glaip_sdk/cli/commands/mcps/get.py +212 -0
  24. glaip_sdk/cli/commands/mcps/list.py +69 -0
  25. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  26. glaip_sdk/cli/commands/mcps/update.py +190 -0
  27. glaip_sdk/cli/commands/models.py +2 -4
  28. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  29. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  30. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  31. glaip_sdk/cli/commands/tools/_common.py +80 -0
  32. glaip_sdk/cli/commands/tools/create.py +228 -0
  33. glaip_sdk/cli/commands/tools/delete.py +61 -0
  34. glaip_sdk/cli/commands/tools/get.py +103 -0
  35. glaip_sdk/cli/commands/tools/list.py +69 -0
  36. glaip_sdk/cli/commands/tools/script.py +49 -0
  37. glaip_sdk/cli/commands/tools/update.py +102 -0
  38. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  39. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  40. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  41. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  42. glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
  43. glaip_sdk/cli/commands/update.py +163 -17
  44. glaip_sdk/cli/config.py +1 -0
  45. glaip_sdk/cli/entrypoint.py +20 -0
  46. glaip_sdk/cli/main.py +112 -35
  47. glaip_sdk/cli/pager.py +3 -3
  48. glaip_sdk/cli/resolution.py +2 -1
  49. glaip_sdk/cli/slash/accounts_controller.py +3 -1
  50. glaip_sdk/cli/slash/agent_session.py +1 -1
  51. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  52. glaip_sdk/cli/slash/session.py +343 -20
  53. glaip_sdk/cli/slash/tui/__init__.py +29 -1
  54. glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
  55. glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
  56. glaip_sdk/cli/slash/tui/clipboard.py +316 -0
  57. glaip_sdk/cli/slash/tui/context.py +92 -0
  58. glaip_sdk/cli/slash/tui/indicators.py +341 -0
  59. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  60. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  61. glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
  62. glaip_sdk/cli/slash/tui/loading.py +43 -21
  63. glaip_sdk/cli/slash/tui/remote_runs_app.py +178 -20
  64. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  65. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  66. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  67. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  68. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  69. glaip_sdk/cli/slash/tui/toast.py +388 -0
  70. glaip_sdk/cli/transcript/history.py +1 -1
  71. glaip_sdk/cli/transcript/viewer.py +1 -1
  72. glaip_sdk/cli/tui_settings.py +125 -0
  73. glaip_sdk/cli/update_notifier.py +215 -7
  74. glaip_sdk/cli/validators.py +1 -1
  75. glaip_sdk/client/__init__.py +2 -1
  76. glaip_sdk/client/_schedule_payloads.py +89 -0
  77. glaip_sdk/client/agents.py +293 -17
  78. glaip_sdk/client/base.py +25 -0
  79. glaip_sdk/client/hitl.py +136 -0
  80. glaip_sdk/client/main.py +7 -5
  81. glaip_sdk/client/mcps.py +44 -13
  82. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  83. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
  84. glaip_sdk/client/payloads/agent/responses.py +43 -0
  85. glaip_sdk/client/run_rendering.py +109 -30
  86. glaip_sdk/client/schedules.py +439 -0
  87. glaip_sdk/client/tools.py +52 -23
  88. glaip_sdk/config/constants.py +22 -2
  89. glaip_sdk/guardrails/__init__.py +80 -0
  90. glaip_sdk/guardrails/serializer.py +91 -0
  91. glaip_sdk/hitl/__init__.py +35 -2
  92. glaip_sdk/hitl/base.py +64 -0
  93. glaip_sdk/hitl/callback.py +43 -0
  94. glaip_sdk/hitl/local.py +1 -31
  95. glaip_sdk/hitl/remote.py +523 -0
  96. glaip_sdk/models/__init__.py +47 -1
  97. glaip_sdk/models/_provider_mappings.py +101 -0
  98. glaip_sdk/models/_validation.py +97 -0
  99. glaip_sdk/models/agent.py +2 -1
  100. glaip_sdk/models/agent_runs.py +2 -1
  101. glaip_sdk/models/constants.py +141 -0
  102. glaip_sdk/models/model.py +170 -0
  103. glaip_sdk/models/schedule.py +224 -0
  104. glaip_sdk/payload_schemas/agent.py +1 -0
  105. glaip_sdk/payload_schemas/guardrails.py +34 -0
  106. glaip_sdk/ptc.py +145 -0
  107. glaip_sdk/registry/tool.py +270 -57
  108. glaip_sdk/runner/__init__.py +20 -3
  109. glaip_sdk/runner/deps.py +4 -1
  110. glaip_sdk/runner/langgraph.py +251 -27
  111. glaip_sdk/runner/logging_config.py +77 -0
  112. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
  113. glaip_sdk/runner/ptc_adapter.py +98 -0
  114. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
  115. glaip_sdk/schedules/__init__.py +22 -0
  116. glaip_sdk/schedules/base.py +291 -0
  117. glaip_sdk/tools/base.py +67 -14
  118. glaip_sdk/utils/__init__.py +1 -0
  119. glaip_sdk/utils/agent_config.py +8 -2
  120. glaip_sdk/utils/bundler.py +138 -2
  121. glaip_sdk/utils/import_resolver.py +427 -49
  122. glaip_sdk/utils/runtime_config.py +3 -2
  123. glaip_sdk/utils/sync.py +31 -11
  124. glaip_sdk/utils/tool_detection.py +274 -6
  125. {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +22 -8
  126. glaip_sdk-0.7.27.dist-info/RECORD +227 -0
  127. {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +1 -1
  128. glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -0
  129. glaip_sdk/cli/commands/agents.py +0 -1509
  130. glaip_sdk/cli/commands/mcps.py +0 -1356
  131. glaip_sdk/cli/commands/tools.py +0 -576
  132. glaip_sdk/cli/utils.py +0 -263
  133. glaip_sdk-0.6.19.dist-info/RECORD +0 -163
  134. glaip_sdk-0.6.19.dist-info/entry_points.txt +0 -2
  135. {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/top_level.txt +0 -0
@@ -6,9 +6,12 @@ Author:
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import importlib.util
10
+ import os
9
11
  import subprocess
10
12
  import sys
11
13
  from collections.abc import Sequence
14
+ from pathlib import Path
12
15
 
13
16
  import click
14
17
  from rich.console import Console
@@ -18,19 +21,132 @@ from glaip_sdk.branding import ACCENT_STYLE, ERROR_STYLE, INFO_STYLE, SUCCESS_ST
18
21
  PACKAGE_NAME = "glaip-sdk"
19
22
 
20
23
 
21
- def _build_upgrade_command(include_prerelease: bool) -> Sequence[str]:
22
- """Return the pip command used to upgrade the SDK."""
23
- command = [
24
- sys.executable,
25
- "-m",
26
- "pip",
27
- "install",
28
- "--upgrade",
29
- PACKAGE_NAME,
30
- ]
24
+ def _is_uv_managed_environment() -> bool:
25
+ """Check if running in a uv-managed tool environment.
26
+
27
+ Uses a path-based heuristic against sys.executable, sys.prefix, and UV_TOOL_DIR
28
+ or UV_TOOL_BIN to detect a case-insensitive "uv/tools" segment. Update if uv
29
+ changes its layout.
30
+ """
31
+ if _has_uv_tool_path(sys.executable):
32
+ return True
33
+ if _has_uv_tool_path(sys.prefix):
34
+ return True
35
+ uv_tool_dir = os.environ.get("UV_TOOL_DIR") or os.environ.get("UV_TOOL_BIN")
36
+ if uv_tool_dir and _has_uv_tool_path(uv_tool_dir):
37
+ return True
38
+ return False
39
+
40
+
41
+ def _has_uv_tool_path(path: str) -> bool:
42
+ """Return True when a path contains a case-insensitive uv/tools segment."""
43
+ parts = [part.lower() for part in Path(path).parts]
44
+ for idx, part in enumerate(parts[:-1]):
45
+ if part == "uv" and parts[idx + 1] == "tools":
46
+ return True
47
+ return False
48
+
49
+
50
+ def _is_pip_available() -> bool:
51
+ """Return True when pip can be imported in the current interpreter."""
52
+ return importlib.util.find_spec("pip") is not None
53
+
54
+
55
+ def _build_missing_pip_guidance(
56
+ *,
57
+ include_prerelease: bool,
58
+ package_name: str = PACKAGE_NAME,
59
+ force_reinstall: bool = False,
60
+ ) -> tuple[str, str]:
61
+ """Return error and troubleshooting guidance when pip is unavailable."""
62
+ manual_cmd = _build_manual_upgrade_command(
63
+ include_prerelease,
64
+ package_name=package_name,
65
+ is_uv=True,
66
+ force_reinstall=force_reinstall,
67
+ )
68
+ error_detail = "pip is not available in this environment."
69
+ troubleshooting = (
70
+ "💡 Troubleshooting:\n"
71
+ f" • If you installed via uv tool, run: {manual_cmd}\n"
72
+ " • Otherwise install pip: python -m ensurepip"
73
+ )
74
+ return error_detail, troubleshooting
75
+
76
+
77
+ def _build_command_parts(
78
+ *,
79
+ package_name: str = PACKAGE_NAME,
80
+ is_uv: bool | None = None,
81
+ force_reinstall: bool = False,
82
+ include_prerelease: bool = False,
83
+ ) -> tuple[list[str], str]:
84
+ """Build the common parts of upgrade commands.
85
+
86
+ Returns:
87
+ Tuple of (command parts list, force_reinstall flag name).
88
+ For uv: (["uv", "tool", "install", "--upgrade", package_name], "--reinstall")
89
+ For pip: (["pip", "install", "--upgrade", package_name], "--force-reinstall")
90
+ """
91
+ if is_uv is None:
92
+ is_uv = _is_uv_managed_environment()
93
+
94
+ if is_uv:
95
+ command = ["uv", "tool", "install", "--upgrade", package_name]
96
+ reinstall_flag = "--reinstall"
97
+ else:
98
+ command = ["pip", "install", "--upgrade", package_name]
99
+ reinstall_flag = "--force-reinstall"
100
+
101
+ if force_reinstall:
102
+ command.insert(-1, reinstall_flag)
103
+
31
104
  if include_prerelease:
32
105
  command.append("--pre")
33
- return command
106
+
107
+ return command, reinstall_flag
108
+
109
+
110
+ def _build_upgrade_command(
111
+ include_prerelease: bool,
112
+ *,
113
+ package_name: str = PACKAGE_NAME,
114
+ is_uv: bool | None = None,
115
+ force_reinstall: bool = False,
116
+ ) -> Sequence[str]:
117
+ """Return the command used to upgrade the SDK (pip or uv tool install)."""
118
+ if is_uv is None:
119
+ is_uv = _is_uv_managed_environment()
120
+
121
+ command_parts, _ = _build_command_parts(
122
+ package_name=package_name,
123
+ is_uv=is_uv,
124
+ force_reinstall=force_reinstall,
125
+ include_prerelease=include_prerelease,
126
+ )
127
+
128
+ # For pip, prepend sys.executable and -m
129
+ if not is_uv:
130
+ command_parts = [sys.executable, "-m"] + command_parts
131
+
132
+ return command_parts
133
+
134
+
135
+ def _build_manual_upgrade_command(
136
+ include_prerelease: bool,
137
+ *,
138
+ package_name: str = PACKAGE_NAME,
139
+ is_uv: bool | None = None,
140
+ force_reinstall: bool = False,
141
+ ) -> str:
142
+ """Return a manual upgrade command string matching the active environment."""
143
+ command_parts, _ = _build_command_parts(
144
+ package_name=package_name,
145
+ is_uv=is_uv,
146
+ force_reinstall=force_reinstall,
147
+ include_prerelease=include_prerelease,
148
+ )
149
+ return " ".join(command_parts)
34
150
 
35
151
 
36
152
  @click.command(name="update")
@@ -40,22 +156,52 @@ def _build_upgrade_command(include_prerelease: bool) -> Sequence[str]:
40
156
  is_flag=True,
41
157
  help="Include pre-release versions when upgrading.",
42
158
  )
43
- def update_command(include_prerelease: bool) -> None:
44
- """Upgrade the glaip-sdk package using pip."""
159
+ @click.option(
160
+ "--reinstall",
161
+ "force_reinstall",
162
+ is_flag=True,
163
+ help="Force reinstall even if already up-to-date.",
164
+ )
165
+ def update_command(include_prerelease: bool, force_reinstall: bool) -> None:
166
+ """Upgrade the glaip-sdk package using pip or uv tool install."""
45
167
  console = Console()
46
- upgrade_cmd = _build_upgrade_command(include_prerelease)
168
+ # Call _is_uv_managed_environment() once and pass explicitly to avoid redundant calls
169
+ is_uv = _is_uv_managed_environment()
170
+ if not is_uv and not _is_pip_available():
171
+ error_detail, troubleshooting = _build_missing_pip_guidance(
172
+ include_prerelease=include_prerelease,
173
+ force_reinstall=force_reinstall,
174
+ )
175
+ raise click.ClickException(f"{error_detail}\n{troubleshooting}")
176
+ upgrade_cmd = _build_upgrade_command(
177
+ include_prerelease,
178
+ is_uv=is_uv,
179
+ force_reinstall=force_reinstall,
180
+ )
181
+
182
+ # Determine the appropriate manual command for error messages
183
+ manual_cmd = _build_manual_upgrade_command(
184
+ include_prerelease,
185
+ is_uv=is_uv,
186
+ force_reinstall=force_reinstall,
187
+ )
188
+
47
189
  console.print(f"[{ACCENT_STYLE}]Upgrading {PACKAGE_NAME} using[/] [{INFO_STYLE}]{' '.join(upgrade_cmd)}[/]")
48
190
 
49
191
  try:
50
192
  subprocess.run(upgrade_cmd, check=True)
51
193
  except FileNotFoundError as exc:
194
+ if is_uv:
195
+ raise click.ClickException(
196
+ f"Unable to locate uv executable. Please ensure uv is installed and on your PATH.\n"
197
+ f"Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh\n"
198
+ f"Or run manually: {manual_cmd}"
199
+ ) from exc
52
200
  raise click.ClickException(
53
201
  "Unable to locate Python executable to run pip. Please ensure Python is installed and try again."
54
202
  ) from exc
55
203
  except subprocess.CalledProcessError as exc:
56
- console.print(
57
- f"[{ERROR_STYLE}]Automatic upgrade failed.[/] Please run `pip install -U {PACKAGE_NAME}` manually."
58
- )
204
+ console.print(f"[{ERROR_STYLE}]Automatic upgrade failed.[/] Please run `{manual_cmd}` manually.")
59
205
  raise click.ClickException("Automatic upgrade failed.") from exc
60
206
 
61
207
  console.print(f"[{SUCCESS_STYLE}]✅ {PACKAGE_NAME} upgraded successfully.[/]")
glaip_sdk/cli/config.py CHANGED
@@ -47,6 +47,7 @@ _PRESERVE_KEYS = {
47
47
  "version",
48
48
  "active_account",
49
49
  "accounts",
50
+ "tui",
50
51
  }
51
52
 
52
53
 
@@ -0,0 +1,20 @@
1
+ """Entry point wrapper for early logging configuration.
2
+
3
+ This must be imported BEFORE glaip_sdk.cli.main to catch import-time warnings.
4
+
5
+ Authors:
6
+ Raymond Christopher (raymond.christopher@gdplabs.id)
7
+ """
8
+
9
+ import sys
10
+
11
+ # Configure logging BEFORE importing anything else
12
+ from glaip_sdk.runner.logging_config import setup_cli_logging
13
+
14
+ setup_cli_logging()
15
+
16
+ # Now import and run CLI
17
+ from glaip_sdk.cli import main # noqa: E402
18
+
19
+ if __name__ == "__main__":
20
+ sys.exit(main()) # pylint: disable=no-value-for-parameter
glaip_sdk/cli/main.py CHANGED
@@ -35,18 +35,29 @@ from glaip_sdk.cli.commands.mcps import mcps_group
35
35
  from glaip_sdk.cli.commands.models import models_group
36
36
  from glaip_sdk.cli.commands.tools import tools_group
37
37
  from glaip_sdk.cli.commands.transcripts import transcripts_group
38
- from glaip_sdk.cli.commands.update import _build_upgrade_command, update_command
38
+ from glaip_sdk.cli.commands.update import (
39
+ _build_missing_pip_guidance,
40
+ _build_manual_upgrade_command,
41
+ _build_upgrade_command,
42
+ _is_pip_available,
43
+ _is_uv_managed_environment,
44
+ update_command,
45
+ )
39
46
  from glaip_sdk.cli.config import load_config
40
47
  from glaip_sdk.cli.hints import in_slash_mode
48
+ from glaip_sdk.cli.core.output import format_size, sdk_version
49
+ from glaip_sdk.cli.core.rendering import spinner_context, update_spinner
41
50
  from glaip_sdk.cli.transcript import get_transcript_cache_stats
42
51
  from glaip_sdk.cli.update_notifier import maybe_notify_update
43
- from glaip_sdk.cli.utils import format_size, sdk_version, spinner_context, update_spinner
44
52
  from glaip_sdk.config.constants import (
45
53
  DEFAULT_AGENT_RUN_TIMEOUT,
46
54
  )
47
55
  from glaip_sdk.icons import ICON_AGENT
48
56
  from glaip_sdk.rich_components import AIPPanel, AIPTable
49
57
 
58
+ # Constants
59
+ UPDATE_ERROR_TITLE = "❌ Update Error"
60
+
50
61
  Client: type[Any] | None = None
51
62
 
52
63
 
@@ -62,6 +73,12 @@ def _resolve_client_class() -> type[Any]:
62
73
 
63
74
  def _suppress_chatty_loggers() -> None:
64
75
  """Silence noisy SDK/httpx logs for CLI output."""
76
+ # Ensure CLI logging is configured (idempotent)
77
+ from glaip_sdk.runner.logging_config import setup_cli_logging # noqa: PLC0415
78
+
79
+ setup_cli_logging()
80
+
81
+ # Also suppress SDK-specific loggers
65
82
  noisy_loggers = [
66
83
  "glaip_sdk.client",
67
84
  "httpx",
@@ -237,26 +254,40 @@ def _load_and_merge_config(ctx: click.Context) -> dict:
237
254
 
238
255
  def _validate_config_and_show_error(config: dict, console: Console) -> None:
239
256
  """Validate configuration and show error if incomplete."""
240
- store = get_account_store()
241
- has_accounts = bool(store.list_accounts())
242
- if not config.get("api_url") or not config.get("api_key"):
243
- no_accounts_hint = "" if has_accounts else "\n • No accounts found; create one now to continue"
244
- console.print(
245
- AIPPanel(
246
- f"[{ERROR_STYLE}]❌ Configuration incomplete[/]\n\n"
247
- f"🔍 Current config:\n"
248
- f" • API URL: {config.get('api_url', 'Not set')}\n"
249
- f" • API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
250
- f"💡 To fix this:\n"
251
- f" Run 'aip accounts add default' to set up credentials\n"
252
- f" • Or run 'aip configure' for interactive setup\n"
253
- f" • Or run 'aip accounts list' to see current accounts{no_accounts_hint}",
254
- title=" Configuration Error",
255
- border_style=ERROR,
256
- ),
257
- )
258
- console.print(f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{sdk_version()}) - Configure to connect")
259
- sys.exit(1)
257
+ if config.get("api_url") and config.get("api_key"):
258
+ return
259
+
260
+ # Best effort: avoid failing validation due to config I/O issues.
261
+ has_accounts = True
262
+ logger = logging.getLogger(__name__)
263
+ try:
264
+ store = get_account_store()
265
+ try:
266
+ has_accounts = bool(store.list_accounts())
267
+ except Exception:
268
+ logger.warning("Failed to list accounts from account store.", exc_info=True)
269
+ has_accounts = True
270
+ except Exception:
271
+ logger.warning("Failed to initialize account store.", exc_info=True)
272
+ has_accounts = True
273
+
274
+ no_accounts_hint = "" if has_accounts else "\n • No accounts found; create one now to continue"
275
+ console.print(
276
+ AIPPanel(
277
+ f"[{ERROR_STYLE}]❌ Configuration incomplete[/]\n\n"
278
+ f"🔍 Current config:\n"
279
+ f" • API URL: {config.get('api_url', 'Not set')}\n"
280
+ f" • API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
281
+ f"💡 To fix this:\n"
282
+ f" • Run 'aip accounts add default' to set up credentials\n"
283
+ f" • Or run 'aip configure' for interactive setup\n"
284
+ f" • Or run 'aip accounts list' to see current accounts{no_accounts_hint}",
285
+ title="❌ Configuration Error",
286
+ border_style=ERROR,
287
+ ),
288
+ )
289
+ console.print(f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{sdk_version()}) - Configure to connect")
290
+ sys.exit(1)
260
291
 
261
292
 
262
293
  def _resolve_status_console(ctx: Any) -> tuple[Console, bool]:
@@ -549,13 +580,39 @@ def update(check_only: bool, force: bool) -> None:
549
580
  ),
550
581
  )
551
582
 
552
- # Update using pip
583
+ # Update using pip or uv tool install
553
584
  try:
554
- cmd = list(_build_upgrade_command(include_prerelease=False))
555
- # Replace package name with "glaip-sdk" (main.py uses different name)
556
- cmd[-1] = "glaip-sdk"
557
- if force:
558
- cmd.insert(5, "--force-reinstall")
585
+ is_uv = _is_uv_managed_environment()
586
+ if not is_uv and not _is_pip_available():
587
+ error_detail, troubleshooting = _build_missing_pip_guidance(
588
+ include_prerelease=False,
589
+ package_name="glaip-sdk",
590
+ force_reinstall=force,
591
+ )
592
+ console.print(
593
+ AIPPanel(
594
+ f"[{ERROR_STYLE}]❌ Update failed[/]\n\n🔍 Error: {error_detail}\n\n{troubleshooting}",
595
+ title=UPDATE_ERROR_TITLE,
596
+ border_style=ERROR,
597
+ padding=(0, 1),
598
+ ),
599
+ )
600
+ sys.exit(1)
601
+ cmd = list(
602
+ _build_upgrade_command(
603
+ include_prerelease=False,
604
+ package_name="glaip-sdk",
605
+ is_uv=is_uv,
606
+ force_reinstall=force,
607
+ )
608
+ )
609
+
610
+ manual_cmd = _build_manual_upgrade_command(
611
+ include_prerelease=False,
612
+ package_name="glaip-sdk",
613
+ is_uv=is_uv,
614
+ force_reinstall=force,
615
+ )
559
616
  subprocess.run(cmd, capture_output=True, text=True, check=True)
560
617
 
561
618
  verify_hint = ""
@@ -582,16 +639,36 @@ def update(check_only: bool, force: bool) -> None:
582
639
  )
583
640
  console.print(f"📋 New version: {version_result.stdout.strip()}")
584
641
 
642
+ except FileNotFoundError:
643
+ troubleshooting = f"💡 Troubleshooting:\n • Try running: {manual_cmd}\n"
644
+ if is_uv:
645
+ troubleshooting += " • Ensure uv is installed: curl -LsSf https://astral.sh/uv/install.sh | sh"
646
+ error_detail = "uv executable not found in your PATH."
647
+ else:
648
+ troubleshooting += " • Ensure Python and pip are installed"
649
+ error_detail = "Python executable not found to run pip."
650
+ console.print(
651
+ AIPPanel(
652
+ f"[{ERROR_STYLE}]❌ Update failed[/]\n\n🔍 Error: {error_detail}\n\n{troubleshooting}",
653
+ title=UPDATE_ERROR_TITLE,
654
+ border_style=ERROR,
655
+ padding=(0, 1),
656
+ ),
657
+ )
658
+ sys.exit(1)
585
659
  except subprocess.CalledProcessError as e:
660
+ troubleshooting = (
661
+ f"💡 Troubleshooting:\n • Check your internet connection\n • Try running: {manual_cmd}\n"
662
+ )
663
+ if is_uv:
664
+ troubleshooting += " • Ensure uv is installed: curl -LsSf https://astral.sh/uv/install.sh | sh"
665
+ else:
666
+ troubleshooting += " • Check if you have write permissions"
667
+
586
668
  console.print(
587
669
  AIPPanel(
588
- f"[{ERROR_STYLE}]❌ Update failed[/]\n\n"
589
- f"🔍 Error: {e.stderr}\n\n"
590
- "💡 Troubleshooting:\n"
591
- " • Check your internet connection\n"
592
- " • Try running: pip install --upgrade glaip-sdk\n"
593
- " • Check if you have write permissions",
594
- title="❌ Update Error",
670
+ f"[{ERROR_STYLE}]❌ Update failed[/]\n\n🔍 Error: {e.stderr}\n\n{troubleshooting}",
671
+ title=UPDATE_ERROR_TITLE,
595
672
  border_style=ERROR,
596
673
  padding=(0, 1),
597
674
  ),
glaip_sdk/cli/pager.py CHANGED
@@ -46,11 +46,11 @@ def _get_console() -> Console:
46
46
  """
47
47
  global console
48
48
  try:
49
- cli_utils = importlib.import_module("glaip_sdk.cli.utils")
49
+ cli_output = importlib.import_module("glaip_sdk.cli.core.output")
50
50
  except Exception: # pragma: no cover - fallback during import cycles
51
- cli_utils = None
51
+ cli_output = None
52
52
 
53
- current_console = getattr(cli_utils, "console", None) if cli_utils else None
53
+ current_console = getattr(cli_output, "console", None) if cli_output else None
54
54
  if current_console is not None and current_console is not console:
55
55
  console = current_console
56
56
 
@@ -13,7 +13,8 @@ from typing import Any
13
13
  import click
14
14
 
15
15
  from glaip_sdk.branding import ACCENT_STYLE
16
- from glaip_sdk.cli.utils import resolve_resource, spinner_context
16
+ from glaip_sdk.cli.core.output import resolve_resource
17
+ from glaip_sdk.cli.core.rendering import spinner_context
17
18
 
18
19
 
19
20
  def resolve_resource_reference(
@@ -170,7 +170,9 @@ class AccountsController:
170
170
  callbacks = AccountsTUICallbacks(switch_account=_switch_in_textual)
171
171
  active = next((row["name"] for row in rows if row.get("active")), None)
172
172
  try:
173
- run_accounts_textual(rows, active_account=active, env_lock=env_lock, callbacks=callbacks)
173
+ # Inject TUI context for theme support
174
+ tui_ctx = getattr(self.session, "tui_ctx", None)
175
+ run_accounts_textual(rows, active_account=active, env_lock=env_lock, callbacks=callbacks, ctx=tui_ctx)
174
176
  except Exception as exc: # pragma: no cover - defensive around Textual failures
175
177
  self.console.print(f"[{WARNING_STYLE}]Accounts browser exited unexpectedly: {exc}[/]")
176
178
 
@@ -17,7 +17,7 @@ from glaip_sdk.cli.commands.agents import run as agents_run_command
17
17
  from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
18
18
  from glaip_sdk.cli.hints import format_command_hint
19
19
  from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
20
- from glaip_sdk.cli.utils import bind_slash_session_context
20
+ from glaip_sdk.cli.core.context import bind_slash_session_context
21
21
 
22
22
  if TYPE_CHECKING: # pragma: no cover - type checking only
23
23
  from glaip_sdk.cli.slash.session import SlashSession
@@ -30,7 +30,7 @@ from glaip_sdk.branding import (
30
30
  )
31
31
  from glaip_sdk.cli.constants import DEFAULT_REMOTE_RUNS_PAGE_LIMIT
32
32
  from glaip_sdk.cli.slash.tui.remote_runs_app import RemoteRunsTUICallbacks, run_remote_runs_textual
33
- from glaip_sdk.cli.utils import prompt_export_choice_questionary, questionary_safe_ask
33
+ from glaip_sdk.cli.core.prompting import prompt_export_choice_questionary, questionary_safe_ask
34
34
  from glaip_sdk.exceptions import (
35
35
  AuthenticationError,
36
36
  ForbiddenError,
@@ -294,12 +294,14 @@ class RemoteRunsController:
294
294
  fetch_detail=fetch_detail,
295
295
  export_run=export_run,
296
296
  )
297
+ tui_ctx = getattr(self.session, "tui_ctx", None)
297
298
  page, limit, cursor = run_remote_runs_textual(
298
299
  runs_page,
299
300
  state.get("cursor", 0),
300
301
  callbacks,
301
302
  agent_name=agent_name,
302
303
  agent_id=agent_id,
304
+ ctx=tui_ctx,
303
305
  )
304
306
  state["page"] = page
305
307
  state["limit"] = limit