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
@@ -0,0 +1,152 @@
1
+ """Create MCP command.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ import click
12
+
13
+ from glaip_sdk.cli.context import output_flags
14
+ from glaip_sdk.cli.display import display_creation_success, handle_json_output, handle_rich_output
15
+ from glaip_sdk.cli.core.context import get_client
16
+ from glaip_sdk.cli.core.rendering import spinner_context
17
+ from glaip_sdk.config.constants import DEFAULT_MCP_TYPE
18
+
19
+ from ._common import (
20
+ _handle_cli_error,
21
+ _load_import_ready_payload,
22
+ _merge_import_payload,
23
+ console,
24
+ mcps_group,
25
+ )
26
+
27
+
28
+ @mcps_group.command()
29
+ @click.option("--name", help="MCP name")
30
+ @click.option("--transport", help="MCP transport protocol")
31
+ @click.option("--description", help="MCP description")
32
+ @click.option(
33
+ "--config",
34
+ help="JSON configuration string or @file reference (e.g., @config.json)",
35
+ )
36
+ @click.option(
37
+ "--auth",
38
+ "--authentication",
39
+ "auth",
40
+ help="JSON authentication object or @file reference (e.g., @auth.json)",
41
+ )
42
+ @click.option(
43
+ "--import",
44
+ "import_file",
45
+ type=click.Path(exists=True, dir_okay=False),
46
+ help="Import MCP configuration from JSON or YAML export",
47
+ )
48
+ @output_flags()
49
+ @click.pass_context
50
+ def create(
51
+ ctx: Any,
52
+ name: str | None,
53
+ transport: str | None,
54
+ description: str | None,
55
+ config: str | None,
56
+ auth: str | None,
57
+ import_file: str | None,
58
+ ) -> None:
59
+ r"""Create a new MCP with specified configuration.
60
+
61
+ You can create an MCP by providing all parameters via CLI options, or by
62
+ importing from a file and optionally overriding specific fields.
63
+
64
+ Args:
65
+ ctx: Click context containing output format preferences
66
+ name: MCP name (required unless provided via --import)
67
+ transport: MCP transport protocol (required unless provided via --import)
68
+ description: Optional MCP description
69
+ config: JSON configuration string or @file reference
70
+ auth: JSON authentication object or @file reference
71
+ import_file: Optional path to import configuration from export file.
72
+ CLI options override imported values.
73
+
74
+ Raises:
75
+ ClickException: If JSON parsing fails or API request fails
76
+
77
+ \b
78
+ Examples:
79
+ Create from CLI options:
80
+ aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
81
+
82
+ Import from file:
83
+ aip mcps create --import mcp-export.json
84
+
85
+ Import with overrides:
86
+ aip mcps create --import mcp-export.json --name new-name --transport sse
87
+ """
88
+ try:
89
+ # Get API client instance for MCP operations
90
+ api_client = get_client(ctx)
91
+
92
+ # Process import file if specified, otherwise use None
93
+ import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
94
+
95
+ merged_payload, missing_fields = _merge_import_payload(
96
+ import_payload,
97
+ cli_name=name,
98
+ cli_transport=transport,
99
+ cli_description=description,
100
+ cli_config=config,
101
+ cli_auth=auth,
102
+ )
103
+
104
+ if missing_fields:
105
+ raise click.ClickException(
106
+ "Missing required fields after combining import and CLI values: " + ", ".join(missing_fields)
107
+ )
108
+
109
+ effective_name = merged_payload["name"]
110
+ effective_transport = merged_payload["transport"]
111
+ effective_description = merged_payload.get("description")
112
+ effective_config = merged_payload.get("config") or {}
113
+ effective_auth = merged_payload.get("authentication")
114
+ mcp_metadata = merged_payload.get("mcp_metadata")
115
+
116
+ with spinner_context(
117
+ ctx,
118
+ "[bold blue]Creating MCP…[/bold blue]",
119
+ console_override=console,
120
+ ):
121
+ # Use SDK client method to create MCP
122
+ create_kwargs: dict[str, Any] = {
123
+ "transport": effective_transport,
124
+ }
125
+ if effective_auth:
126
+ create_kwargs["authentication"] = effective_auth
127
+ if mcp_metadata is not None:
128
+ create_kwargs["mcp_metadata"] = mcp_metadata
129
+
130
+ mcp = api_client.mcps.create_mcp(
131
+ name=effective_name,
132
+ description=effective_description,
133
+ config=effective_config,
134
+ **create_kwargs,
135
+ )
136
+
137
+ # Handle JSON output
138
+ handle_json_output(ctx, mcp.model_dump())
139
+
140
+ # Handle Rich output
141
+ rich_panel = display_creation_success(
142
+ "MCP",
143
+ mcp.name,
144
+ mcp.id,
145
+ Type=getattr(mcp, "type", DEFAULT_MCP_TYPE),
146
+ Transport=getattr(mcp, "transport", effective_transport),
147
+ Description=effective_description or "No description",
148
+ )
149
+ handle_rich_output(ctx, rich_panel)
150
+
151
+ except Exception as e:
152
+ _handle_cli_error(ctx, e, "MCP creation")
@@ -0,0 +1,73 @@
1
+ """Delete MCP command.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ import click
12
+
13
+ from glaip_sdk.cli.context import output_flags
14
+ from glaip_sdk.cli.display import (
15
+ display_confirmation_prompt,
16
+ display_deletion_success,
17
+ handle_json_output,
18
+ handle_rich_output,
19
+ )
20
+ from glaip_sdk.cli.core.context import get_client
21
+ from glaip_sdk.cli.core.rendering import spinner_context
22
+
23
+ from ._common import _handle_cli_error, _resolve_mcp, console, mcps_group
24
+
25
+
26
+ @mcps_group.command()
27
+ @click.argument("mcp_ref")
28
+ @click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
29
+ @output_flags()
30
+ @click.pass_context
31
+ def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
32
+ """Delete an MCP after confirmation.
33
+
34
+ Args:
35
+ ctx: Click context containing output format preferences
36
+ mcp_ref: MCP reference (ID or name)
37
+ yes: Skip confirmation prompt if True
38
+
39
+ Raises:
40
+ ClickException: If MCP not found or deletion fails
41
+
42
+ Note:
43
+ Requires confirmation unless --yes flag is provided.
44
+ Deletion is permanent and cannot be undone.
45
+ """
46
+ try:
47
+ client = get_client(ctx)
48
+
49
+ # Resolve MCP using helper function
50
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
51
+
52
+ # Confirm deletion
53
+ if not yes and not display_confirmation_prompt("MCP", mcp.name):
54
+ return
55
+
56
+ with spinner_context(
57
+ ctx,
58
+ "[bold blue]Deleting MCP…[/bold blue]",
59
+ console_override=console,
60
+ ):
61
+ client.mcps.delete_mcp(mcp.id)
62
+
63
+ handle_json_output(
64
+ ctx,
65
+ {
66
+ "success": True,
67
+ "message": f"MCP '{mcp.name}' deleted",
68
+ },
69
+ )
70
+ handle_rich_output(ctx, display_deletion_success("MCP", mcp.name))
71
+
72
+ except Exception as e:
73
+ _handle_cli_error(ctx, e, "MCP deletion")
@@ -0,0 +1,212 @@
1
+ """Get MCP command.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import click
13
+
14
+ from glaip_sdk.branding import SUCCESS_STYLE, WARNING_STYLE
15
+ from glaip_sdk.cli.context import detect_export_format, output_flags
16
+ from glaip_sdk.cli.core.context import get_client
17
+ from glaip_sdk.cli.core.output import fetch_resource_for_export, format_datetime_fields, output_result
18
+ from glaip_sdk.cli.core.rendering import spinner_context
19
+ from glaip_sdk.cli.io import fetch_raw_resource_details
20
+ from glaip_sdk.cli.rich_helpers import print_markup
21
+ from glaip_sdk.utils.serialization import build_mcp_export_payload, write_resource_export
22
+ import sys
23
+
24
+ from ._common import _resolve_mcp, console, mcps_group
25
+
26
+
27
+ def _handle_mcp_export(
28
+ ctx: Any,
29
+ client: Any,
30
+ mcp: Any,
31
+ export_path: Path,
32
+ no_auth_prompt: bool,
33
+ auth_placeholder: str,
34
+ ) -> None:
35
+ """Handle MCP export to file with format detection and auth handling.
36
+
37
+ Args:
38
+ ctx: Click context for spinner management
39
+ client: API client for fetching MCP details
40
+ mcp: MCP object to export
41
+ export_path: Target file path (format detected from extension)
42
+ no_auth_prompt: Skip interactive secret prompts if True
43
+ auth_placeholder: Placeholder text for missing secrets
44
+
45
+ Note:
46
+ Supports JSON (.json) and YAML (.yaml/.yml) export formats.
47
+ In interactive mode, prompts for secret values.
48
+ In non-interactive mode, uses placeholder values.
49
+ """
50
+ # Auto-detect format from file extension
51
+ detected_format = detect_export_format(export_path)
52
+
53
+ # Always export comprehensive data - re-fetch with full details
54
+ mcp = fetch_resource_for_export(
55
+ ctx,
56
+ mcp,
57
+ resource_type="MCP",
58
+ get_by_id_func=client.mcps.get_mcp_by_id,
59
+ console_override=console,
60
+ )
61
+
62
+ # Determine if we should prompt for secrets
63
+ prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
64
+
65
+ # Warn user if non-interactive mode forces placeholder usage
66
+ if not no_auth_prompt and not sys.stdin.isatty():
67
+ print_markup(
68
+ f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. Using placeholder values for secrets.[/]",
69
+ console=console,
70
+ )
71
+
72
+ # Build and write export payload
73
+ if prompt_for_secrets:
74
+ # Interactive mode: no spinner during prompts
75
+ export_payload = build_mcp_export_payload(
76
+ mcp,
77
+ prompt_for_secrets=prompt_for_secrets,
78
+ placeholder=auth_placeholder,
79
+ console=console,
80
+ )
81
+ with spinner_context(
82
+ ctx,
83
+ "[bold blue]Writing export file…[/bold blue]",
84
+ console_override=console,
85
+ ):
86
+ write_resource_export(export_path, export_payload, detected_format)
87
+ else:
88
+ # Non-interactive mode: spinner for entire export process
89
+ with spinner_context(
90
+ ctx,
91
+ "[bold blue]Exporting MCP configuration…[/bold blue]",
92
+ console_override=console,
93
+ ):
94
+ export_payload = build_mcp_export_payload(
95
+ mcp,
96
+ prompt_for_secrets=prompt_for_secrets,
97
+ placeholder=auth_placeholder,
98
+ console=console,
99
+ )
100
+ write_resource_export(export_path, export_payload, detected_format)
101
+
102
+ print_markup(
103
+ f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/]",
104
+ console=console,
105
+ )
106
+
107
+
108
+ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
109
+ """Display MCP details using raw API data or fallback to Pydantic model.
110
+
111
+ Args:
112
+ ctx: Click context containing output format preferences
113
+ client: API client for fetching raw MCP data
114
+ mcp: MCP object to display details for
115
+
116
+ Note:
117
+ Attempts to fetch raw API data first to preserve all fields.
118
+ Falls back to Pydantic model data if raw data unavailable.
119
+ Formats datetime fields for better readability.
120
+ """
121
+ # Try to fetch raw API data first to preserve ALL fields
122
+ with spinner_context(
123
+ ctx,
124
+ "[bold blue]Fetching detailed MCP data…[/bold blue]",
125
+ console_override=console,
126
+ ):
127
+ raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
128
+
129
+ if raw_mcp_data:
130
+ # Use raw API data - this preserves ALL fields
131
+ formatted_data = format_datetime_fields(raw_mcp_data)
132
+
133
+ output_result(
134
+ ctx,
135
+ formatted_data,
136
+ title="MCP Details",
137
+ panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
138
+ )
139
+ else:
140
+ # Fall back to Pydantic model data
141
+ console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
142
+ result_data = {
143
+ "id": str(getattr(mcp, "id", "N/A")),
144
+ "name": getattr(mcp, "name", "N/A"),
145
+ "type": getattr(mcp, "type", "N/A"),
146
+ "config": getattr(mcp, "config", "N/A"),
147
+ "status": getattr(mcp, "status", "N/A"),
148
+ "connection_status": getattr(mcp, "connection_status", "N/A"),
149
+ }
150
+ output_result(ctx, result_data, title=f"🔌 {mcp.name}")
151
+
152
+
153
+ @mcps_group.command()
154
+ @click.argument("mcp_ref")
155
+ @click.option(
156
+ "--export",
157
+ type=click.Path(dir_okay=False, writable=True),
158
+ help="Export complete MCP configuration to file (format auto-detected from .json/.yaml extension)",
159
+ )
160
+ @click.option(
161
+ "--no-auth-prompt",
162
+ is_flag=True,
163
+ help="Skip interactive secret prompts and use placeholder values.",
164
+ )
165
+ @click.option(
166
+ "--auth-placeholder",
167
+ default="<INSERT VALUE>",
168
+ show_default=True,
169
+ help="Placeholder text used when secrets are unavailable.",
170
+ )
171
+ @output_flags()
172
+ @click.pass_context
173
+ def get(
174
+ ctx: Any,
175
+ mcp_ref: str,
176
+ export: str | None,
177
+ no_auth_prompt: bool,
178
+ auth_placeholder: str,
179
+ ) -> None:
180
+ r"""Get MCP details and optionally export configuration to file.
181
+
182
+ Args:
183
+ ctx: Click context containing output format preferences
184
+ mcp_ref: MCP reference (ID or name)
185
+ export: Optional file path to export MCP configuration
186
+ no_auth_prompt: Skip interactive secret prompts if True
187
+ auth_placeholder: Placeholder text for missing secrets
188
+
189
+ Raises:
190
+ ClickException: If MCP not found or export fails
191
+
192
+ \b
193
+ Examples:
194
+ aip mcps get my-mcp
195
+ aip mcps get my-mcp --export mcp.json # Export as JSON
196
+ aip mcps get my-mcp --export mcp.yaml # Export as YAML
197
+ """
198
+ try:
199
+ client = get_client(ctx)
200
+
201
+ # Resolve MCP using helper function
202
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
203
+
204
+ # Handle export option
205
+ if export:
206
+ _handle_mcp_export(ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder)
207
+
208
+ # Display MCP details
209
+ _display_mcp_details(ctx, client, mcp)
210
+
211
+ except Exception as e:
212
+ raise click.ClickException(str(e)) from e
@@ -0,0 +1,69 @@
1
+ """List MCPs command.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ import click
12
+
13
+ from glaip_sdk.branding import ACCENT_STYLE, INFO
14
+ from glaip_sdk.cli.context import output_flags
15
+ from glaip_sdk.cli.core.output import coerce_to_row, output_list
16
+ from glaip_sdk.cli.core.rendering import with_client_and_spinner
17
+
18
+ from ._common import console, mcps_group
19
+
20
+
21
+ @mcps_group.command(name="list")
22
+ @output_flags()
23
+ @click.pass_context
24
+ def list_mcps(ctx: Any) -> None:
25
+ """List all MCPs in a formatted table.
26
+
27
+ Args:
28
+ ctx: Click context containing output format preferences
29
+
30
+ Raises:
31
+ ClickException: If API request fails
32
+ """
33
+ try:
34
+ with with_client_and_spinner(
35
+ ctx,
36
+ "[bold blue]Fetching MCPs…[/bold blue]",
37
+ console_override=console,
38
+ ) as client:
39
+ mcps = client.mcps.list_mcps()
40
+
41
+ # Define table columns: (data_key, header, style, width)
42
+ columns = [
43
+ ("id", "ID", "dim", 36),
44
+ ("name", "Name", ACCENT_STYLE, None),
45
+ ("config", "Config", INFO, None),
46
+ ]
47
+
48
+ # Transform function for safe dictionary access
49
+ def transform_mcp(mcp: Any) -> dict[str, Any]:
50
+ """Transform an MCP object to a display row dictionary.
51
+
52
+ Args:
53
+ mcp: MCP object to transform.
54
+
55
+ Returns:
56
+ Dictionary with id, name, and config fields.
57
+ """
58
+ row = coerce_to_row(mcp, ["id", "name", "config"])
59
+ # Ensure id is always a string
60
+ row["id"] = str(row["id"])
61
+ # Truncate config field for display
62
+ if row["config"] != "N/A":
63
+ row["config"] = str(row["config"])[:50] + "..." if len(str(row["config"])) > 50 else str(row["config"])
64
+ return row
65
+
66
+ output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
67
+
68
+ except Exception as e:
69
+ raise click.ClickException(str(e)) from e