glaip-sdk 0.1.2__py3-none-any.whl → 0.7.17__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 (217) hide show
  1. glaip_sdk/__init__.py +44 -4
  2. glaip_sdk/_version.py +9 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1413 -0
  5. glaip_sdk/branding.py +126 -2
  6. glaip_sdk/cli/account_store.py +555 -0
  7. glaip_sdk/cli/auth.py +260 -15
  8. glaip_sdk/cli/commands/__init__.py +2 -2
  9. glaip_sdk/cli/commands/accounts.py +746 -0
  10. glaip_sdk/cli/commands/agents/__init__.py +116 -0
  11. glaip_sdk/cli/commands/agents/_common.py +562 -0
  12. glaip_sdk/cli/commands/agents/create.py +155 -0
  13. glaip_sdk/cli/commands/agents/delete.py +64 -0
  14. glaip_sdk/cli/commands/agents/get.py +89 -0
  15. glaip_sdk/cli/commands/agents/list.py +129 -0
  16. glaip_sdk/cli/commands/agents/run.py +264 -0
  17. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  18. glaip_sdk/cli/commands/agents/update.py +112 -0
  19. glaip_sdk/cli/commands/common_config.py +104 -0
  20. glaip_sdk/cli/commands/configure.py +728 -113
  21. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  22. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  23. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  24. glaip_sdk/cli/commands/mcps/create.py +152 -0
  25. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  26. glaip_sdk/cli/commands/mcps/get.py +212 -0
  27. glaip_sdk/cli/commands/mcps/list.py +69 -0
  28. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  29. glaip_sdk/cli/commands/mcps/update.py +190 -0
  30. glaip_sdk/cli/commands/models.py +12 -8
  31. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  32. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  33. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  34. glaip_sdk/cli/commands/tools/_common.py +80 -0
  35. glaip_sdk/cli/commands/tools/create.py +228 -0
  36. glaip_sdk/cli/commands/tools/delete.py +61 -0
  37. glaip_sdk/cli/commands/tools/get.py +103 -0
  38. glaip_sdk/cli/commands/tools/list.py +69 -0
  39. glaip_sdk/cli/commands/tools/script.py +49 -0
  40. glaip_sdk/cli/commands/tools/update.py +102 -0
  41. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  42. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  43. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  44. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  45. glaip_sdk/cli/commands/transcripts_original.py +756 -0
  46. glaip_sdk/cli/commands/update.py +163 -17
  47. glaip_sdk/cli/config.py +49 -4
  48. glaip_sdk/cli/constants.py +38 -0
  49. glaip_sdk/cli/context.py +8 -0
  50. glaip_sdk/cli/core/__init__.py +79 -0
  51. glaip_sdk/cli/core/context.py +124 -0
  52. glaip_sdk/cli/core/output.py +851 -0
  53. glaip_sdk/cli/core/prompting.py +649 -0
  54. glaip_sdk/cli/core/rendering.py +187 -0
  55. glaip_sdk/cli/display.py +41 -20
  56. glaip_sdk/cli/entrypoint.py +20 -0
  57. glaip_sdk/cli/hints.py +57 -0
  58. glaip_sdk/cli/io.py +6 -3
  59. glaip_sdk/cli/main.py +340 -143
  60. glaip_sdk/cli/masking.py +21 -33
  61. glaip_sdk/cli/pager.py +12 -13
  62. glaip_sdk/cli/parsers/__init__.py +1 -3
  63. glaip_sdk/cli/resolution.py +2 -1
  64. glaip_sdk/cli/slash/__init__.py +0 -9
  65. glaip_sdk/cli/slash/accounts_controller.py +580 -0
  66. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  67. glaip_sdk/cli/slash/agent_session.py +62 -21
  68. glaip_sdk/cli/slash/prompt.py +21 -0
  69. glaip_sdk/cli/slash/remote_runs_controller.py +568 -0
  70. glaip_sdk/cli/slash/session.py +1105 -153
  71. glaip_sdk/cli/slash/tui/__init__.py +36 -0
  72. glaip_sdk/cli/slash/tui/accounts.tcss +177 -0
  73. glaip_sdk/cli/slash/tui/accounts_app.py +1853 -0
  74. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  75. glaip_sdk/cli/slash/tui/clipboard.py +195 -0
  76. glaip_sdk/cli/slash/tui/context.py +92 -0
  77. glaip_sdk/cli/slash/tui/indicators.py +341 -0
  78. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  79. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  80. glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
  81. glaip_sdk/cli/slash/tui/loading.py +80 -0
  82. glaip_sdk/cli/slash/tui/remote_runs_app.py +760 -0
  83. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  84. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  85. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  86. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  87. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  88. glaip_sdk/cli/slash/tui/toast.py +388 -0
  89. glaip_sdk/cli/transcript/__init__.py +12 -52
  90. glaip_sdk/cli/transcript/cache.py +255 -44
  91. glaip_sdk/cli/transcript/capture.py +66 -1
  92. glaip_sdk/cli/transcript/history.py +815 -0
  93. glaip_sdk/cli/transcript/viewer.py +72 -463
  94. glaip_sdk/cli/tui_settings.py +125 -0
  95. glaip_sdk/cli/update_notifier.py +227 -10
  96. glaip_sdk/cli/validators.py +5 -6
  97. glaip_sdk/client/__init__.py +3 -1
  98. glaip_sdk/client/_schedule_payloads.py +89 -0
  99. glaip_sdk/client/agent_runs.py +147 -0
  100. glaip_sdk/client/agents.py +576 -44
  101. glaip_sdk/client/base.py +26 -0
  102. glaip_sdk/client/hitl.py +136 -0
  103. glaip_sdk/client/main.py +25 -14
  104. glaip_sdk/client/mcps.py +165 -24
  105. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  106. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +63 -47
  107. glaip_sdk/client/payloads/agent/responses.py +43 -0
  108. glaip_sdk/client/run_rendering.py +546 -92
  109. glaip_sdk/client/schedules.py +439 -0
  110. glaip_sdk/client/shared.py +21 -0
  111. glaip_sdk/client/tools.py +206 -32
  112. glaip_sdk/config/constants.py +33 -2
  113. glaip_sdk/guardrails/__init__.py +80 -0
  114. glaip_sdk/guardrails/serializer.py +89 -0
  115. glaip_sdk/hitl/__init__.py +48 -0
  116. glaip_sdk/hitl/base.py +64 -0
  117. glaip_sdk/hitl/callback.py +43 -0
  118. glaip_sdk/hitl/local.py +121 -0
  119. glaip_sdk/hitl/remote.py +523 -0
  120. glaip_sdk/mcps/__init__.py +21 -0
  121. glaip_sdk/mcps/base.py +345 -0
  122. glaip_sdk/models/__init__.py +136 -0
  123. glaip_sdk/models/_provider_mappings.py +101 -0
  124. glaip_sdk/models/_validation.py +97 -0
  125. glaip_sdk/models/agent.py +48 -0
  126. glaip_sdk/models/agent_runs.py +117 -0
  127. glaip_sdk/models/common.py +42 -0
  128. glaip_sdk/models/constants.py +141 -0
  129. glaip_sdk/models/mcp.py +33 -0
  130. glaip_sdk/models/model.py +170 -0
  131. glaip_sdk/models/schedule.py +224 -0
  132. glaip_sdk/models/tool.py +33 -0
  133. glaip_sdk/payload_schemas/__init__.py +1 -13
  134. glaip_sdk/payload_schemas/agent.py +1 -0
  135. glaip_sdk/payload_schemas/guardrails.py +34 -0
  136. glaip_sdk/registry/__init__.py +55 -0
  137. glaip_sdk/registry/agent.py +164 -0
  138. glaip_sdk/registry/base.py +139 -0
  139. glaip_sdk/registry/mcp.py +253 -0
  140. glaip_sdk/registry/tool.py +445 -0
  141. glaip_sdk/rich_components.py +58 -2
  142. glaip_sdk/runner/__init__.py +76 -0
  143. glaip_sdk/runner/base.py +84 -0
  144. glaip_sdk/runner/deps.py +115 -0
  145. glaip_sdk/runner/langgraph.py +1055 -0
  146. glaip_sdk/runner/logging_config.py +77 -0
  147. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  148. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  149. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  150. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +116 -0
  151. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  152. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  153. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
  154. glaip_sdk/schedules/__init__.py +22 -0
  155. glaip_sdk/schedules/base.py +291 -0
  156. glaip_sdk/tools/__init__.py +22 -0
  157. glaip_sdk/tools/base.py +488 -0
  158. glaip_sdk/utils/__init__.py +59 -12
  159. glaip_sdk/utils/a2a/__init__.py +34 -0
  160. glaip_sdk/utils/a2a/event_processor.py +188 -0
  161. glaip_sdk/utils/agent_config.py +8 -2
  162. glaip_sdk/utils/bundler.py +403 -0
  163. glaip_sdk/utils/client.py +111 -0
  164. glaip_sdk/utils/client_utils.py +39 -7
  165. glaip_sdk/utils/datetime_helpers.py +58 -0
  166. glaip_sdk/utils/discovery.py +78 -0
  167. glaip_sdk/utils/display.py +23 -15
  168. glaip_sdk/utils/export.py +143 -0
  169. glaip_sdk/utils/general.py +0 -33
  170. glaip_sdk/utils/import_export.py +12 -7
  171. glaip_sdk/utils/import_resolver.py +524 -0
  172. glaip_sdk/utils/instructions.py +101 -0
  173. glaip_sdk/utils/rendering/__init__.py +115 -1
  174. glaip_sdk/utils/rendering/formatting.py +5 -30
  175. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  176. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  177. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  178. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  179. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  180. glaip_sdk/utils/rendering/models.py +1 -0
  181. glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
  182. glaip_sdk/utils/rendering/renderer/base.py +299 -1434
  183. glaip_sdk/utils/rendering/renderer/config.py +1 -5
  184. glaip_sdk/utils/rendering/renderer/debug.py +26 -20
  185. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  186. glaip_sdk/utils/rendering/renderer/stream.py +4 -33
  187. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  188. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  189. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  190. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  191. glaip_sdk/utils/rendering/state.py +204 -0
  192. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  193. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
  194. glaip_sdk/utils/rendering/steps/format.py +176 -0
  195. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  196. glaip_sdk/utils/rendering/timing.py +36 -0
  197. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  198. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  199. glaip_sdk/utils/resource_refs.py +25 -13
  200. glaip_sdk/utils/runtime_config.py +426 -0
  201. glaip_sdk/utils/serialization.py +18 -0
  202. glaip_sdk/utils/sync.py +162 -0
  203. glaip_sdk/utils/tool_detection.py +301 -0
  204. glaip_sdk/utils/tool_storage_provider.py +140 -0
  205. glaip_sdk/utils/validation.py +16 -24
  206. {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +69 -23
  207. glaip_sdk-0.7.17.dist-info/RECORD +224 -0
  208. {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
  209. glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
  210. glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
  211. glaip_sdk/cli/commands/agents.py +0 -1369
  212. glaip_sdk/cli/commands/mcps.py +0 -1187
  213. glaip_sdk/cli/commands/tools.py +0 -584
  214. glaip_sdk/cli/utils.py +0 -1278
  215. glaip_sdk/models.py +0 -240
  216. glaip_sdk-0.1.2.dist-info/RECORD +0 -82
  217. glaip_sdk-0.1.2.dist-info/entry_points.txt +0 -3
glaip_sdk/cli/auth.py CHANGED
@@ -1,24 +1,25 @@
1
- """Authentication export helpers for MCP CLI commands.
1
+ """Authentication export helpers for MCP CLI commands and credential resolution.
2
2
 
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.
6
+ It also provides credential resolution for the AIP CLI, supporting multiple
7
+ account profiles and environment variable overrides.
9
8
 
10
9
  Authors:
11
10
  Raymond Christopher (raymond.christopher@gdplabs.id)
12
11
  """
13
12
 
14
- from collections.abc import Iterable, Mapping
13
+ import os
14
+ from collections.abc import Callable, Iterable, Mapping
15
15
  from typing import Any
16
16
 
17
17
  import click
18
18
  from rich.console import Console
19
19
 
20
20
  from glaip_sdk.branding import HINT_PREFIX_STYLE, WARNING_STYLE
21
- from glaip_sdk.cli.utils import command_hint, format_command_hint
21
+ from glaip_sdk.cli.account_store import AccountNotFoundError, AccountStoreError, get_account_store
22
+ from glaip_sdk.cli.hints import command_hint, format_command_hint
22
23
 
23
24
 
24
25
  def prepare_authentication_export(
@@ -107,6 +108,24 @@ def _get_token_value(prompt_for_secrets: bool, placeholder: str, console: Consol
107
108
  return placeholder
108
109
 
109
110
 
111
+ def _normalize_header_keys(
112
+ header_keys: Iterable[str] | str | None,
113
+ *,
114
+ default: Iterable[str] | None = None,
115
+ ) -> list[str]:
116
+ """Normalize header_keys to a list, handling strings and None safely."""
117
+ if header_keys is None:
118
+ return list(default or [])
119
+ if isinstance(header_keys, str):
120
+ return [header_keys] if header_keys else list(default or [])
121
+ try:
122
+ return list(header_keys)
123
+ except TypeError:
124
+ raise click.ClickException(
125
+ f"Invalid header_keys type: expected string or iterable, got {type(header_keys).__name__}"
126
+ ) from None
127
+
128
+
110
129
  def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, str]:
111
130
  """Build headers for bearer token authentication.
112
131
 
@@ -118,7 +137,10 @@ def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, s
118
137
  A dictionary of HTTP headers including the Authorization header when
119
138
  applicable.
120
139
  """
121
- header_keys = auth.get("header_keys", ["Authorization"])
140
+ default_header_keys = ["Authorization"]
141
+ has_header_keys = "header_keys" in auth
142
+ header_keys_raw = auth.get("header_keys") if has_header_keys else default_header_keys
143
+ header_keys = _normalize_header_keys(header_keys_raw, default=None if has_header_keys else default_header_keys)
122
144
  headers = {}
123
145
  for key in header_keys:
124
146
  # Prepend "Bearer " if this is Authorization header
@@ -177,8 +199,8 @@ def _extract_api_key_name(auth: dict[str, Any]) -> str | None:
177
199
  """
178
200
  key_name = auth.get("key")
179
201
  if not key_name and "header_keys" in auth:
180
- header_keys = auth["header_keys"]
181
- if isinstance(header_keys, list) and header_keys:
202
+ header_keys = _normalize_header_keys(auth["header_keys"])
203
+ if header_keys:
182
204
  key_name = header_keys[0]
183
205
  return key_name
184
206
 
@@ -226,8 +248,12 @@ def _build_api_key_headers(auth: dict[str, Any], key_name: str | None, key_value
226
248
  Returns:
227
249
  A dictionary of HTTP headers for API key authentication.
228
250
  """
229
- header_keys = auth.get("header_keys", [key_name] if key_name else [])
230
- return {key: key_value for key in header_keys}
251
+ default_header_keys = [key_name] if key_name else []
252
+ has_header_keys = "header_keys" in auth
253
+ header_keys_raw = auth.get("header_keys") if has_header_keys else default_header_keys
254
+ header_keys_list = _normalize_header_keys(header_keys_raw, default=None if has_header_keys else default_header_keys)
255
+ filtered_keys = [k for k in header_keys_list if k]
256
+ return dict.fromkeys(filtered_keys, key_value)
231
257
 
232
258
 
233
259
  def _prepare_api_key_auth(
@@ -313,9 +339,7 @@ def _extract_header_names(existing_headers: Mapping[str, Any] | None, header_key
313
339
  """
314
340
  if existing_headers:
315
341
  return list(existing_headers.keys())
316
- if header_keys:
317
- return list(header_keys)
318
- return []
342
+ return _normalize_header_keys(header_keys)
319
343
 
320
344
 
321
345
  def _is_valid_secret(value: Any) -> bool:
@@ -435,7 +459,7 @@ def _prompt_secret_with_placeholder(
435
459
  )
436
460
 
437
461
  attempts = 0
438
- while attempts <= retry_limit:
462
+ while attempts <= retry_limit: # pragma: no cover
439
463
  response = click.prompt(
440
464
  prompt_message,
441
465
  default="",
@@ -458,3 +482,224 @@ def _prompt_secret_with_placeholder(
458
482
 
459
483
  # This line is unreachable as the loop always returns
460
484
  # return placeholder
485
+
486
+
487
+ # ----------------------------- Credential Resolution ----------------------------- #
488
+
489
+
490
+ def resolve_api_url_from_context(
491
+ ctx: Any,
492
+ *,
493
+ get_api_url: Callable[[Any], str | None] | None = None,
494
+ get_account_name: Callable[[Any], str | None] | None = None,
495
+ ) -> str | None:
496
+ """Resolve API URL from context using account store (CLI/palette ignores env creds).
497
+
498
+ Helper function to extract API URL from various context formats.
499
+ Used by transcript capture and slash session to avoid code duplication.
500
+
501
+ Args:
502
+ ctx: Context object (can be dict, click.Context, or any object with attributes).
503
+ get_api_url: Optional function to extract api_url from context.
504
+ If None, tries ctx.obj.get("api_url") or ctx.get("api_url").
505
+ get_account_name: Optional function to extract account_name from context.
506
+ If None, tries ctx.obj.get("account_name") or ctx.get("account_name").
507
+
508
+ Returns:
509
+ Resolved API URL or None.
510
+ """
511
+ api_url = None
512
+ account_name = None
513
+
514
+ if get_api_url:
515
+ api_url = get_api_url(ctx)
516
+ elif isinstance(ctx, dict):
517
+ api_url = ctx.get("api_url")
518
+ elif hasattr(ctx, "obj") and isinstance(ctx.obj, dict):
519
+ api_url = ctx.obj.get("api_url")
520
+
521
+ if get_account_name:
522
+ account_name = get_account_name(ctx)
523
+ elif isinstance(ctx, dict):
524
+ account_name = ctx.get("account_name")
525
+ elif hasattr(ctx, "obj") and isinstance(ctx.obj, dict):
526
+ account_name = ctx.obj.get("account_name")
527
+
528
+ if isinstance(api_url, str) and api_url.strip():
529
+ return api_url.strip()
530
+
531
+ try:
532
+ resolved_url, _, _ = resolve_credentials(
533
+ account_name=account_name,
534
+ api_url=None,
535
+ api_key=None,
536
+ ignore_env_creds=True,
537
+ )
538
+ except Exception:
539
+ return None
540
+
541
+ return resolved_url
542
+
543
+
544
+ def _resolve_account_name(account_name: str | None) -> str | None:
545
+ """Resolve account name from parameter (env var removed for CLI/palette)."""
546
+ return account_name
547
+
548
+
549
+ def _validate_account_exists(account_name: str | None, store: Any) -> None:
550
+ """Validate that the specified account exists.
551
+
552
+ Raises:
553
+ AccountNotFoundError: If account_name is specified but account doesn't exist.
554
+ """
555
+ if account_name:
556
+ account = store.get_account(account_name)
557
+ if not account:
558
+ raise AccountNotFoundError(
559
+ f"Account '{account_name}' not found. Run 'aip accounts list' to see available accounts."
560
+ )
561
+
562
+
563
+ def _merge_credentials(
564
+ api_url: str | None,
565
+ api_key: str | None,
566
+ profile_url: str | None,
567
+ profile_key: str | None,
568
+ ignore_env_creds: bool,
569
+ ) -> tuple[str | None, str | None]:
570
+ """Merge credentials from multiple sources.
571
+
572
+ Args:
573
+ api_url: Explicit API URL override.
574
+ api_key: Explicit API key override.
575
+ profile_url: Profile API URL.
576
+ profile_key: Profile API key.
577
+ ignore_env_creds: If True, ignore env vars.
578
+
579
+ Returns:
580
+ Tuple of (final_url, final_key).
581
+ """
582
+ if not ignore_env_creds:
583
+ env_url = os.getenv("AIP_API_URL")
584
+ env_key = os.getenv("AIP_API_KEY")
585
+ final_url = api_url or env_url or profile_url
586
+ final_key = api_key or env_key or profile_key
587
+ else:
588
+ final_url = api_url or profile_url
589
+ final_key = api_key or profile_key
590
+ return final_url, final_key
591
+
592
+
593
+ def _determine_source(
594
+ api_url: str | None,
595
+ api_key: str | None,
596
+ account_name: str | None,
597
+ store: Any,
598
+ ) -> str:
599
+ """Determine the source of credentials.
600
+
601
+ Returns:
602
+ Source string describing where credentials came from.
603
+ """
604
+ if api_url or api_key:
605
+ return "flag"
606
+ if account_name:
607
+ return f"account:{account_name}"
608
+ active = store.get_active_account()
609
+ return f"active_profile:{active}" if active else "none"
610
+
611
+
612
+ _ENV_WARNING_EMITTED = False
613
+
614
+
615
+ def _maybe_warn_env_creds_ignored(ignore_env_creds: bool) -> None:
616
+ """Emit a one-time warning when env credentials are present but ignored."""
617
+ global _ENV_WARNING_EMITTED
618
+
619
+ if _ENV_WARNING_EMITTED or not ignore_env_creds:
620
+ return
621
+
622
+ if os.getenv("AIP_API_URL") or os.getenv("AIP_API_KEY"):
623
+ click.echo(
624
+ "Warning: CLI ignores AIP_API_URL/AIP_API_KEY; use account profiles via 'aip accounts add/use'. "
625
+ "Python SDK callers can opt in with ignore_env_creds=False.",
626
+ err=True,
627
+ )
628
+ _ENV_WARNING_EMITTED = True
629
+
630
+
631
+ def resolve_credentials(
632
+ account_name: str | None = None,
633
+ api_url: str | None = None,
634
+ api_key: str | None = None,
635
+ *,
636
+ ignore_env_creds: bool = True,
637
+ ) -> tuple[str | None, str | None, str]:
638
+ """Resolve credentials from multiple sources with precedence.
639
+
640
+ For CLI/palette: ignores raw credential env vars (AIP_API_URL/AIP_API_KEY),
641
+ and only uses explicit account selection (no AIP_ACCOUNT env). Python SDK can use
642
+ ignore_env_creds=False to honor env vars if needed.
643
+
644
+ Precedence order (CLI/palette):
645
+ 1. Explicit parameters (api_url, api_key)
646
+ 2. Account profile (from account_name or active_account)
647
+
648
+ Args:
649
+ account_name: Account name to use, or None for active account.
650
+ api_url: Explicit API URL override.
651
+ api_key: Explicit API key override.
652
+ ignore_env_creds: If True (default), ignore AIP_API_URL/AIP_API_KEY env vars.
653
+
654
+ Returns:
655
+ Tuple of (api_url, api_key, source) where source describes where
656
+ credentials came from (e.g., "flag", "active_profile", "account:name").
657
+
658
+ Raises:
659
+ click.ClickException: If a requested account does not exist.
660
+ """
661
+ _maybe_warn_env_creds_ignored(ignore_env_creds)
662
+
663
+ # 1. Explicit parameters take highest precedence
664
+ if api_url and api_key:
665
+ return api_url, api_key, "flag"
666
+
667
+ # 2. Account profile resolution
668
+ account_name = _resolve_account_name(account_name)
669
+ store = get_account_store()
670
+ try:
671
+ _validate_account_exists(account_name, store)
672
+ except AccountNotFoundError as exc:
673
+ raise click.ClickException(str(exc)) from exc
674
+
675
+ try:
676
+ profile_url, profile_key = store.get_credentials(account_name)
677
+ except AccountStoreError:
678
+ profile_url, profile_key = None, None
679
+
680
+ final_url, final_key = _merge_credentials(api_url, api_key, profile_url, profile_key, ignore_env_creds)
681
+ source = _determine_source(api_url, api_key, account_name, store)
682
+
683
+ return final_url, final_key, source
684
+
685
+
686
+ def get_credentials(
687
+ account_name: str | None = None,
688
+ api_url: str | None = None,
689
+ api_key: str | None = None,
690
+ ) -> tuple[str | None, str | None]:
691
+ """Get credentials for CLI commands (backward compatible wrapper).
692
+
693
+ This function maintains backward compatibility with existing code that
694
+ expects (url, key) tuple. For source information, use resolve_credentials.
695
+
696
+ Args:
697
+ account_name: Account name to use, or None for active account.
698
+ api_url: Explicit API URL override.
699
+ api_key: Explicit API key override.
700
+
701
+ Returns:
702
+ Tuple of (api_url, api_key).
703
+ """
704
+ url, key, _ = resolve_credentials(account_name, api_url, api_key)
705
+ return url, key
@@ -1,5 +1,5 @@
1
1
  """CLI commands package exports."""
2
2
 
3
- from glaip_sdk.cli.commands import agents, configure, mcps, models, tools, update
3
+ from glaip_sdk.cli.commands import agents, configure, mcps, models, tools, transcripts, update
4
4
 
5
- __all__ = ["agents", "configure", "mcps", "models", "tools", "update"]
5
+ __all__ = ["agents", "configure", "mcps", "models", "tools", "transcripts", "update"]