glaip-sdk 0.6.15b2__py3-none-any.whl → 0.6.15b3__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 (154) hide show
  1. glaip_sdk/agents/__init__.py +27 -0
  2. glaip_sdk/agents/base.py +1196 -0
  3. glaip_sdk/cli/__init__.py +9 -0
  4. glaip_sdk/cli/account_store.py +540 -0
  5. glaip_sdk/cli/agent_config.py +78 -0
  6. glaip_sdk/cli/auth.py +699 -0
  7. glaip_sdk/cli/commands/__init__.py +5 -0
  8. glaip_sdk/cli/commands/accounts.py +746 -0
  9. glaip_sdk/cli/commands/agents.py +1509 -0
  10. glaip_sdk/cli/commands/common_config.py +104 -0
  11. glaip_sdk/cli/commands/configure.py +896 -0
  12. glaip_sdk/cli/commands/mcps.py +1356 -0
  13. glaip_sdk/cli/commands/models.py +69 -0
  14. glaip_sdk/cli/commands/tools.py +576 -0
  15. glaip_sdk/cli/commands/transcripts.py +755 -0
  16. glaip_sdk/cli/commands/update.py +61 -0
  17. glaip_sdk/cli/config.py +95 -0
  18. glaip_sdk/cli/constants.py +38 -0
  19. glaip_sdk/cli/context.py +150 -0
  20. glaip_sdk/cli/core/__init__.py +79 -0
  21. glaip_sdk/cli/core/context.py +124 -0
  22. glaip_sdk/cli/core/output.py +851 -0
  23. glaip_sdk/cli/core/prompting.py +649 -0
  24. glaip_sdk/cli/core/rendering.py +187 -0
  25. glaip_sdk/cli/display.py +355 -0
  26. glaip_sdk/cli/hints.py +57 -0
  27. glaip_sdk/cli/io.py +112 -0
  28. glaip_sdk/cli/main.py +615 -0
  29. glaip_sdk/cli/masking.py +136 -0
  30. glaip_sdk/cli/mcp_validators.py +287 -0
  31. glaip_sdk/cli/pager.py +266 -0
  32. glaip_sdk/cli/parsers/__init__.py +7 -0
  33. glaip_sdk/cli/parsers/json_input.py +177 -0
  34. glaip_sdk/cli/resolution.py +67 -0
  35. glaip_sdk/cli/rich_helpers.py +27 -0
  36. glaip_sdk/cli/slash/__init__.py +15 -0
  37. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  38. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  39. glaip_sdk/cli/slash/agent_session.py +285 -0
  40. glaip_sdk/cli/slash/prompt.py +256 -0
  41. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  42. glaip_sdk/cli/slash/session.py +1708 -0
  43. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  44. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  45. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  46. glaip_sdk/cli/slash/tui/loading.py +58 -0
  47. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  48. glaip_sdk/cli/transcript/__init__.py +31 -0
  49. glaip_sdk/cli/transcript/cache.py +536 -0
  50. glaip_sdk/cli/transcript/capture.py +329 -0
  51. glaip_sdk/cli/transcript/export.py +38 -0
  52. glaip_sdk/cli/transcript/history.py +815 -0
  53. glaip_sdk/cli/transcript/launcher.py +77 -0
  54. glaip_sdk/cli/transcript/viewer.py +374 -0
  55. glaip_sdk/cli/update_notifier.py +290 -0
  56. glaip_sdk/cli/utils.py +263 -0
  57. glaip_sdk/cli/validators.py +238 -0
  58. glaip_sdk/client/__init__.py +11 -0
  59. glaip_sdk/client/_agent_payloads.py +520 -0
  60. glaip_sdk/client/agent_runs.py +147 -0
  61. glaip_sdk/client/agents.py +1335 -0
  62. glaip_sdk/client/base.py +502 -0
  63. glaip_sdk/client/main.py +249 -0
  64. glaip_sdk/client/mcps.py +370 -0
  65. glaip_sdk/client/run_rendering.py +700 -0
  66. glaip_sdk/client/shared.py +21 -0
  67. glaip_sdk/client/tools.py +661 -0
  68. glaip_sdk/client/validators.py +198 -0
  69. glaip_sdk/config/constants.py +52 -0
  70. glaip_sdk/mcps/__init__.py +21 -0
  71. glaip_sdk/mcps/base.py +345 -0
  72. glaip_sdk/models/__init__.py +90 -0
  73. glaip_sdk/models/agent.py +47 -0
  74. glaip_sdk/models/agent_runs.py +116 -0
  75. glaip_sdk/models/common.py +42 -0
  76. glaip_sdk/models/mcp.py +33 -0
  77. glaip_sdk/models/tool.py +33 -0
  78. glaip_sdk/payload_schemas/__init__.py +7 -0
  79. glaip_sdk/payload_schemas/agent.py +85 -0
  80. glaip_sdk/registry/__init__.py +55 -0
  81. glaip_sdk/registry/agent.py +164 -0
  82. glaip_sdk/registry/base.py +139 -0
  83. glaip_sdk/registry/mcp.py +253 -0
  84. glaip_sdk/registry/tool.py +232 -0
  85. glaip_sdk/runner/__init__.py +59 -0
  86. glaip_sdk/runner/base.py +84 -0
  87. glaip_sdk/runner/deps.py +112 -0
  88. glaip_sdk/runner/langgraph.py +782 -0
  89. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  90. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  91. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  92. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  93. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  94. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  95. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  96. glaip_sdk/tools/__init__.py +22 -0
  97. glaip_sdk/tools/base.py +435 -0
  98. glaip_sdk/utils/__init__.py +86 -0
  99. glaip_sdk/utils/a2a/__init__.py +34 -0
  100. glaip_sdk/utils/a2a/event_processor.py +188 -0
  101. glaip_sdk/utils/agent_config.py +194 -0
  102. glaip_sdk/utils/bundler.py +267 -0
  103. glaip_sdk/utils/client.py +111 -0
  104. glaip_sdk/utils/client_utils.py +486 -0
  105. glaip_sdk/utils/datetime_helpers.py +58 -0
  106. glaip_sdk/utils/discovery.py +78 -0
  107. glaip_sdk/utils/display.py +135 -0
  108. glaip_sdk/utils/export.py +143 -0
  109. glaip_sdk/utils/general.py +61 -0
  110. glaip_sdk/utils/import_export.py +168 -0
  111. glaip_sdk/utils/import_resolver.py +492 -0
  112. glaip_sdk/utils/instructions.py +101 -0
  113. glaip_sdk/utils/rendering/__init__.py +115 -0
  114. glaip_sdk/utils/rendering/formatting.py +264 -0
  115. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  116. glaip_sdk/utils/rendering/layout/panels.py +156 -0
  117. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  118. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  119. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  120. glaip_sdk/utils/rendering/models.py +85 -0
  121. glaip_sdk/utils/rendering/renderer/__init__.py +55 -0
  122. glaip_sdk/utils/rendering/renderer/base.py +1024 -0
  123. glaip_sdk/utils/rendering/renderer/config.py +27 -0
  124. glaip_sdk/utils/rendering/renderer/console.py +55 -0
  125. glaip_sdk/utils/rendering/renderer/debug.py +178 -0
  126. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  127. glaip_sdk/utils/rendering/renderer/stream.py +202 -0
  128. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  129. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  130. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  131. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  132. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  133. glaip_sdk/utils/rendering/state.py +204 -0
  134. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  135. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  136. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  137. glaip_sdk/utils/rendering/steps/format.py +176 -0
  138. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  139. glaip_sdk/utils/rendering/timing.py +36 -0
  140. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  141. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  142. glaip_sdk/utils/resource_refs.py +195 -0
  143. glaip_sdk/utils/run_renderer.py +41 -0
  144. glaip_sdk/utils/runtime_config.py +425 -0
  145. glaip_sdk/utils/serialization.py +424 -0
  146. glaip_sdk/utils/sync.py +142 -0
  147. glaip_sdk/utils/tool_detection.py +33 -0
  148. glaip_sdk/utils/validation.py +264 -0
  149. {glaip_sdk-0.6.15b2.dist-info → glaip_sdk-0.6.15b3.dist-info}/METADATA +1 -1
  150. glaip_sdk-0.6.15b3.dist-info/RECORD +160 -0
  151. glaip_sdk-0.6.15b2.dist-info/RECORD +0 -12
  152. {glaip_sdk-0.6.15b2.dist-info → glaip_sdk-0.6.15b3.dist-info}/WHEEL +0 -0
  153. {glaip_sdk-0.6.15b2.dist-info → glaip_sdk-0.6.15b3.dist-info}/entry_points.txt +0 -0
  154. {glaip_sdk-0.6.15b2.dist-info → glaip_sdk-0.6.15b3.dist-info}/top_level.txt +0 -0
glaip_sdk/cli/main.py ADDED
@@ -0,0 +1,615 @@
1
+ """Main CLI entry point for AIP SDK.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ import logging
8
+ import subprocess
9
+ import sys
10
+ from typing import Any
11
+
12
+ import click
13
+ from rich.console import Console
14
+ from glaip_sdk.branding import (
15
+ ERROR,
16
+ ERROR_STYLE,
17
+ INFO,
18
+ INFO_STYLE,
19
+ NEUTRAL,
20
+ SUCCESS,
21
+ SUCCESS_STYLE,
22
+ WARNING,
23
+ WARNING_STYLE,
24
+ AIPBranding,
25
+ )
26
+ from glaip_sdk.cli.account_store import get_account_store
27
+ from glaip_sdk.cli.auth import resolve_credentials
28
+ from glaip_sdk.cli.commands.accounts import accounts_group
29
+ from glaip_sdk.cli.commands.agents import agents_group
30
+ from glaip_sdk.cli.commands.configure import (
31
+ config_group,
32
+ configure_command,
33
+ )
34
+ from glaip_sdk.cli.commands.mcps import mcps_group
35
+ from glaip_sdk.cli.commands.models import models_group
36
+ from glaip_sdk.cli.commands.tools import tools_group
37
+ from glaip_sdk.cli.commands.transcripts import transcripts_group
38
+ from glaip_sdk.cli.commands.update import _build_upgrade_command, update_command
39
+ from glaip_sdk.cli.config import load_config
40
+ from glaip_sdk.cli.hints import in_slash_mode
41
+ from glaip_sdk.cli.transcript import get_transcript_cache_stats
42
+ 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
+ from glaip_sdk.config.constants import (
45
+ DEFAULT_AGENT_RUN_TIMEOUT,
46
+ )
47
+ from glaip_sdk.icons import ICON_AGENT
48
+ from glaip_sdk.rich_components import AIPPanel, AIPTable
49
+
50
+ Client: type[Any] | None = None
51
+
52
+
53
+ def _resolve_client_class() -> type[Any]:
54
+ """Resolve the Client class lazily to avoid heavy imports at CLI startup."""
55
+ global Client
56
+ if Client is None:
57
+ from glaip_sdk import Client as ClientClass # noqa: PLC0415
58
+
59
+ Client = ClientClass
60
+ return Client
61
+
62
+
63
+ def _suppress_chatty_loggers() -> None:
64
+ """Silence noisy SDK/httpx logs for CLI output."""
65
+ noisy_loggers = [
66
+ "glaip_sdk.client",
67
+ "httpx",
68
+ "httpcore",
69
+ ]
70
+ for name in noisy_loggers:
71
+ logger = logging.getLogger(name)
72
+ # Respect existing configuration: only raise level when unset,
73
+ # and avoid changing propagation if a custom handler is already attached.
74
+ if logger.level == logging.NOTSET:
75
+ logger.setLevel(logging.WARNING)
76
+ if not logger.handlers:
77
+ logger.propagate = False
78
+
79
+
80
+ # Import SlashSession for potential mocking in tests
81
+ try:
82
+ from glaip_sdk.cli.slash import SlashSession
83
+ except ImportError: # pragma: no cover - optional slash dependencies
84
+ # Slash dependencies might not be available in all environments
85
+ SlashSession = None
86
+
87
+ # Constants
88
+ AVAILABLE_STATUS = "āœ… Available"
89
+
90
+
91
+ @click.group(invoke_without_command=True)
92
+ @click.version_option(package_name="glaip-sdk", prog_name="aip")
93
+ @click.option(
94
+ "--api-url",
95
+ help="(Deprecated) AIP API URL; use profiles via --account instead",
96
+ hidden=True,
97
+ )
98
+ @click.option(
99
+ "--api-key",
100
+ help="(Deprecated) AIP API Key; use profiles via --account instead",
101
+ hidden=True,
102
+ )
103
+ @click.option("--timeout", default=30.0, help="Request timeout in seconds")
104
+ @click.option(
105
+ "--view",
106
+ "view",
107
+ type=click.Choice(["rich", "plain", "json", "md"]),
108
+ default="rich",
109
+ help="Output view format",
110
+ )
111
+ @click.option("--no-tty", is_flag=True, help="Disable TTY renderer")
112
+ @click.option(
113
+ "--account",
114
+ "account_name",
115
+ help="Target a named account profile for this command",
116
+ hidden=True, # Hidden by default, shown with --help --all
117
+ )
118
+ @click.pass_context
119
+ def main(
120
+ ctx: Any,
121
+ api_url: str | None,
122
+ api_key: str | None,
123
+ timeout: float | None,
124
+ view: str | None,
125
+ no_tty: bool,
126
+ account_name: str | None,
127
+ ) -> None:
128
+ r"""GL AIP SDK Command Line Interface.
129
+
130
+ A comprehensive CLI for managing GL AIP resources including
131
+ agents, tools, MCPs, and more.
132
+
133
+ \b
134
+ Examples:
135
+ aip version # Show detailed version info
136
+ aip configure # Configure credentials
137
+ aip accounts add prod # Add account profile
138
+ aip accounts use staging # Switch account
139
+ aip agents list # List all agents
140
+ aip tools create my_tool.py # Create a new tool
141
+ aip agents run my-agent "Hello world" # Run an agent
142
+
143
+ \b
144
+ NEW: Store multiple accounts via 'aip accounts add' and switch with 'aip accounts use'.
145
+ """
146
+ # Store configuration in context
147
+ ctx.ensure_object(dict)
148
+ ctx.obj["api_url"] = api_url
149
+ ctx.obj["api_key"] = api_key
150
+ ctx.obj["timeout"] = timeout
151
+ ctx.obj["view"] = view
152
+ ctx.obj["account_name"] = account_name
153
+
154
+ _suppress_chatty_loggers()
155
+
156
+ ctx.obj["tty"] = not no_tty
157
+
158
+ launching_slash = (
159
+ ctx.invoked_subcommand is None
160
+ and not ctx.resilient_parsing
161
+ and _should_launch_slash(ctx)
162
+ and SlashSession is not None
163
+ )
164
+
165
+ if not ctx.resilient_parsing and ctx.obj["tty"] and not launching_slash:
166
+ console = Console()
167
+ preferred_console = maybe_notify_update(
168
+ sdk_version(),
169
+ console=console,
170
+ ctx=ctx,
171
+ slash_command="update",
172
+ )
173
+ ctx.obj["_preferred_console"] = preferred_console or console
174
+
175
+ if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
176
+ if launching_slash:
177
+ session = SlashSession(ctx)
178
+ session.run()
179
+ ctx.exit()
180
+ else:
181
+ click.echo(ctx.get_help())
182
+ ctx.exit()
183
+
184
+
185
+ # Add command groups
186
+ main.add_command(accounts_group)
187
+ main.add_command(agents_group)
188
+ main.add_command(config_group)
189
+ main.add_command(tools_group)
190
+ main.add_command(mcps_group)
191
+ main.add_command(models_group)
192
+ main.add_command(transcripts_group)
193
+
194
+ # Add top-level commands
195
+ main.add_command(configure_command)
196
+ main.add_command(update_command)
197
+
198
+
199
+ # Tip: `--version` is provided by click.version_option above.
200
+
201
+
202
+ def _should_launch_slash(ctx: click.Context) -> bool:
203
+ """Determine whether to open the command palette automatically."""
204
+ ctx_obj = ctx.obj or {}
205
+ if not bool(ctx_obj.get("tty", True)):
206
+ return False
207
+
208
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
209
+ return False
210
+
211
+ return True
212
+
213
+
214
+ def _load_and_merge_config(ctx: click.Context) -> dict:
215
+ """Load configuration from multiple sources and merge them."""
216
+ context_config = ctx.obj or {}
217
+ account_name = context_config.get("account_name")
218
+
219
+ # Resolve credentials using new account store system
220
+ api_url, api_key, source = resolve_credentials(
221
+ account_name=account_name,
222
+ api_url=context_config.get("api_url"),
223
+ api_key=context_config.get("api_key"),
224
+ )
225
+
226
+ # Load other config values (timeout, etc.) from legacy config
227
+ legacy_config = load_config()
228
+ timeout = context_config.get("timeout") or legacy_config.get("timeout")
229
+
230
+ return {
231
+ "api_url": api_url,
232
+ "api_key": api_key,
233
+ "timeout": timeout,
234
+ "_source": source, # Track where credentials came from
235
+ }
236
+
237
+
238
+ def _validate_config_and_show_error(config: dict, console: Console) -> None:
239
+ """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)
260
+
261
+
262
+ def _resolve_status_console(ctx: Any) -> tuple[Console, bool]:
263
+ """Return the console to use and whether we are in slash mode."""
264
+ ctx_obj = ctx.obj if isinstance(ctx.obj, dict) else None
265
+ console_override = ctx_obj.get("_slash_console") if ctx_obj else None
266
+ preferred_console = ctx_obj.get("_preferred_console") if ctx_obj else None
267
+ if preferred_console is None:
268
+ # In heavily mocked tests, maybe_notify_update may be patched with a return_value
269
+ preferred_console = getattr(maybe_notify_update, "return_value", None)
270
+ console = console_override or preferred_console or Console()
271
+ slash_mode = in_slash_mode(ctx)
272
+ return console, slash_mode
273
+
274
+
275
+ def _render_status_heading(console: Console, slash_mode: bool, config: dict) -> bool:
276
+ """Print the status heading/banner.
277
+
278
+ Returns True if a generic ready line was printed (to avoid duplication).
279
+ """
280
+ del slash_mode # heading now consistent across invocation contexts
281
+ ready_printed = False
282
+ console.print(f"[{INFO_STYLE}]GL AIP status[/]")
283
+ console.print("")
284
+
285
+ # Show account information
286
+ source = str(config.get("_source") or "unknown")
287
+ account_name = None
288
+ if source.startswith("account:") or source.startswith("active_profile:"):
289
+ account_name = source.split(":", 1)[1]
290
+
291
+ if account_name:
292
+ store = get_account_store()
293
+ account = store.get_account(account_name)
294
+ if account:
295
+ url = account.get("api_url", "")
296
+ # Format source to match spec: "active_profile" instead of "active_profile:name"
297
+ display_source = source.split(":")[0] if ":" in source else source
298
+ console.print(f"[{SUCCESS_STYLE}]Account: {account_name} (source={display_source}) Ā· API URL: {url}[/]")
299
+ else:
300
+ console.print(f"[{SUCCESS_STYLE}]āœ… GL AIP ready[/] (SDK v{sdk_version()})")
301
+ ready_printed = True
302
+ elif source == "flag":
303
+ console.print(f"[{SUCCESS_STYLE}]Account: (source={source})[/]")
304
+ else:
305
+ console.print(f"[{SUCCESS_STYLE}]āœ… GL AIP ready[/] (SDK v{sdk_version()})")
306
+ ready_printed = True
307
+
308
+ return ready_printed
309
+
310
+
311
+ def _collect_cache_summary() -> tuple[str | None, str | None]:
312
+ """Collect transcript cache summary and optional note."""
313
+ try:
314
+ cache_stats = get_transcript_cache_stats()
315
+ except Exception:
316
+ return "[dim]Saved transcripts[/dim]: unavailable", None
317
+
318
+ runs_text = f"{cache_stats.entry_count} runs saved"
319
+ if cache_stats.total_bytes:
320
+ size_part = f" Ā· {format_size(cache_stats.total_bytes)} used"
321
+ else:
322
+ size_part = ""
323
+
324
+ cache_line = f"[dim]Saved transcripts[/dim]: {runs_text}{size_part} Ā· {cache_stats.cache_dir}"
325
+ return cache_line, None
326
+
327
+
328
+ def _display_cache_summary(console: Console, slash_mode: bool, cache_line: str | None, cache_note: str | None) -> None:
329
+ """Render the cache summary details."""
330
+ if cache_line:
331
+ console.print(cache_line)
332
+ if cache_note and not slash_mode:
333
+ console.print(cache_note)
334
+
335
+
336
+ def _safe_list_call(obj: Any, attr: str) -> list[Any]:
337
+ """Call list-like client methods defensively, returning an empty list on failure."""
338
+ func = getattr(obj, attr, None)
339
+ if callable(func):
340
+ try:
341
+ return func()
342
+ except Exception as exc:
343
+ logging.getLogger(__name__).debug(
344
+ "Failed to call %s on %s: %s", attr, type(obj).__name__, exc, exc_info=True
345
+ )
346
+ return []
347
+ return []
348
+
349
+
350
+ def _get_client_from_config(config: dict) -> Any:
351
+ """Return a Client instance built from config."""
352
+ client_class = _resolve_client_class()
353
+ return client_class(
354
+ api_url=config["api_url"],
355
+ api_key=config["api_key"],
356
+ timeout=config.get("timeout", 30.0),
357
+ )
358
+
359
+
360
+ def _create_and_test_client(config: dict, console: Console, *, compact: bool = False) -> Any:
361
+ """Create client and test connection by fetching resources."""
362
+ client: Any = _get_client_from_config(config)
363
+
364
+ # Test connection by listing resources with a spinner where available
365
+ try:
366
+ with spinner_context(
367
+ None,
368
+ "[bold blue]Checking GL AIP status…[/bold blue]",
369
+ console_override=console,
370
+ spinner_style=INFO,
371
+ ) as status_indicator:
372
+ update_spinner(status_indicator, "[bold blue]Fetching agents…[/bold blue]")
373
+ agents = client.list_agents()
374
+
375
+ update_spinner(status_indicator, "[bold blue]Fetching tools…[/bold blue]")
376
+ tools = client.list_tools()
377
+
378
+ update_spinner(status_indicator, "[bold blue]Fetching MCPs…[/bold blue]")
379
+ mcps = client.list_mcps()
380
+ except Exception as e:
381
+ # Show AIP Ready status even if connection fails
382
+ if compact:
383
+ status_text = "API call failed"
384
+ api_url = getattr(client, "api_url", config.get("api_url", ""))
385
+ console.print(f"[dim]• Base URL[/dim]: {api_url} ({status_text})")
386
+ console.print(f"[{ERROR_STYLE}]• Error[/]: {e}")
387
+ console.print("[dim]• Tip[/dim]: Check network connectivity or API permissions and try again.")
388
+ console.print("[dim]• Resources[/dim]: unavailable")
389
+ else:
390
+ api_url = getattr(client, "api_url", config.get("api_url", ""))
391
+ console.print(
392
+ AIPPanel(
393
+ f"[{WARNING_STYLE}]āš ļø Connection established but API call failed[/]\n"
394
+ f"šŸ”— API URL: {api_url}\n"
395
+ f"āŒ Error: {e}\n\n"
396
+ f"šŸ’” This usually means:\n"
397
+ f" • Network connectivity issues\n"
398
+ f" • API permissions problems\n"
399
+ f" • Backend service issues",
400
+ title="āš ļø Partial Connection",
401
+ border_style=WARNING,
402
+ ),
403
+ )
404
+ return client
405
+
406
+ # Create status table
407
+ table = AIPTable(title="šŸ”— GL AIP Status")
408
+ table.add_column("Resource", style=INFO, width=15)
409
+ table.add_column("Count", style=NEUTRAL, width=10)
410
+ table.add_column("Status", style=SUCCESS_STYLE, width=15)
411
+
412
+ table.add_row("Agents", str(len(agents)), AVAILABLE_STATUS)
413
+ table.add_row("Tools", str(len(tools)), AVAILABLE_STATUS)
414
+ table.add_row("MCPs", str(len(mcps)), AVAILABLE_STATUS)
415
+
416
+ if compact:
417
+ connection_summary = "GL AIP reachable"
418
+ console.print(f"[dim]• Base URL[/dim]: {client.api_url} ({connection_summary})")
419
+ console.print(f"[dim]• Agent timeout[/dim]: {DEFAULT_AGENT_RUN_TIMEOUT}s")
420
+ console.print(f"[dim]• Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}")
421
+ else:
422
+ console.print( # pragma: no cover - UI display formatting
423
+ AIPPanel(
424
+ f"[{SUCCESS_STYLE}]āœ… Connected to GL AIP[/]\n"
425
+ f"šŸ”— API URL: {client.api_url}\n"
426
+ f"{ICON_AGENT} Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
427
+ title="šŸš€ Connection Status",
428
+ border_style=SUCCESS,
429
+ ),
430
+ )
431
+
432
+ console.print(table) # pragma: no cover - UI display formatting
433
+
434
+ return client
435
+
436
+
437
+ def _handle_connection_error(config: dict, console: Console, error: Exception) -> None:
438
+ """Handle connection errors and show troubleshooting information."""
439
+ console.print(
440
+ AIPPanel(
441
+ f"[{ERROR_STYLE}]āŒ Connection failed[/]\n\n"
442
+ f"šŸ” Error: {error}\n\n"
443
+ f"šŸ’” Troubleshooting steps:\n"
444
+ f" • Verify your API URL and key are correct\n"
445
+ f" • Check network connectivity to {config.get('api_url', 'your API')}\n"
446
+ f" • Run 'aip configure' to update credentials\n"
447
+ f" • Run 'aip config list' to check configuration",
448
+ title="āŒ Connection Error",
449
+ border_style=ERROR,
450
+ ),
451
+ )
452
+ # Log and return; callers decide whether to exit.
453
+
454
+
455
+ @main.command()
456
+ @click.option(
457
+ "--account",
458
+ "account_name",
459
+ help="Target a named account profile for this command",
460
+ )
461
+ @click.pass_context
462
+ def status(ctx: Any, account_name: str | None) -> None:
463
+ """Show connection status and basic info."""
464
+ config: dict = {}
465
+ console: Console | None = None
466
+ try:
467
+ if account_name:
468
+ if ctx.obj is None:
469
+ ctx.obj = {}
470
+ ctx.obj["account_name"] = account_name
471
+
472
+ console, slash_mode = _resolve_status_console(ctx)
473
+
474
+ # Load and merge configuration
475
+ config = _load_and_merge_config(ctx)
476
+
477
+ ready_printed = _render_status_heading(console, slash_mode, config)
478
+ if not ready_printed:
479
+ console.print(f"[{SUCCESS_STYLE}]āœ… GL AIP ready[/] (SDK v{sdk_version()})")
480
+
481
+ cache_result = _collect_cache_summary()
482
+ if isinstance(cache_result, tuple) and len(cache_result) == 2:
483
+ cache_line, cache_note = cache_result
484
+ else:
485
+ cache_line, cache_note = cache_result, None
486
+ _display_cache_summary(console, slash_mode, cache_line, cache_note)
487
+
488
+ # Validate configuration
489
+ _validate_config_and_show_error(config, console)
490
+
491
+ # Create and test client connection using unified compact layout
492
+ client = _create_and_test_client(config, console, compact=True)
493
+ close = getattr(client, "close", None)
494
+ if callable(close):
495
+ try:
496
+ close()
497
+ except Exception:
498
+ pass
499
+
500
+ except Exception as e:
501
+ # Handle any unexpected errors during the process and exit with error code
502
+ fallback_console = console or Console()
503
+ _handle_connection_error(config or {}, fallback_console, e)
504
+ sys.exit(1)
505
+
506
+
507
+ @main.command()
508
+ def version() -> None:
509
+ """Show version information."""
510
+ branding = AIPBranding.create_from_sdk(sdk_version=sdk_version(), package_name="glaip-sdk")
511
+ branding.display_version_panel()
512
+
513
+
514
+ @main.command()
515
+ @click.option("--check-only", is_flag=True, help="Only check for updates without installing")
516
+ @click.option(
517
+ "--force",
518
+ is_flag=True,
519
+ help="Force reinstall even if already up-to-date (adds --force-reinstall)",
520
+ )
521
+ def update(check_only: bool, force: bool) -> None:
522
+ """Update AIP SDK to the latest version from PyPI."""
523
+ slash_mode = in_slash_mode()
524
+ try:
525
+ console = Console()
526
+
527
+ if check_only:
528
+ console.print(
529
+ AIPPanel(
530
+ "[bold blue]šŸ” Checking for updates...[/bold blue]\n\nšŸ’” To install updates, run: aip update",
531
+ title="šŸ“‹ Update Check",
532
+ border_style="blue",
533
+ ),
534
+ )
535
+ return
536
+
537
+ update_hint = ""
538
+ if not slash_mode:
539
+ update_hint = "\nšŸ’” Use --check-only to just check for updates"
540
+
541
+ console.print(
542
+ AIPPanel(
543
+ "[bold blue]šŸ”„ Updating AIP SDK...[/bold blue]\n\n"
544
+ "šŸ“¦ This will update the package from PyPI"
545
+ f"{update_hint}",
546
+ title="Update Process",
547
+ border_style="blue",
548
+ padding=(0, 1),
549
+ ),
550
+ )
551
+
552
+ # Update using pip
553
+ 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")
559
+ subprocess.run(cmd, capture_output=True, text=True, check=True)
560
+
561
+ verify_hint = ""
562
+ if not slash_mode:
563
+ verify_hint = "\nšŸ’” Restart your terminal or run 'aip --version' to verify"
564
+
565
+ console.print(
566
+ AIPPanel(
567
+ f"[{SUCCESS_STYLE}]āœ… Update successful![/]\n\n"
568
+ "šŸ”„ AIP SDK has been updated to the latest version"
569
+ f"{verify_hint}",
570
+ title="šŸŽ‰ Update Complete",
571
+ border_style=SUCCESS,
572
+ padding=(0, 1),
573
+ ),
574
+ )
575
+
576
+ # Show new version
577
+ version_result = subprocess.run(
578
+ [sys.executable, "-m", "glaip_sdk.cli.main", "--version"],
579
+ capture_output=True,
580
+ text=True,
581
+ check=True,
582
+ )
583
+ console.print(f"šŸ“‹ New version: {version_result.stdout.strip()}")
584
+
585
+ except subprocess.CalledProcessError as e:
586
+ console.print(
587
+ 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",
595
+ border_style=ERROR,
596
+ padding=(0, 1),
597
+ ),
598
+ )
599
+ sys.exit(1)
600
+
601
+ except ImportError:
602
+ console.print(
603
+ AIPPanel(
604
+ f"[{ERROR_STYLE}]āŒ Rich library not available[/]\n\n"
605
+ "šŸ’” Install rich: pip install rich\n"
606
+ " Then try: aip update",
607
+ title="āŒ Missing Dependency",
608
+ border_style=ERROR,
609
+ ),
610
+ )
611
+ sys.exit(1)
612
+
613
+
614
+ if __name__ == "__main__":
615
+ main() # pylint: disable=no-value-for-parameter