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
@@ -1,584 +0,0 @@
1
- """Tool management commands.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- import json
8
- import re
9
- from pathlib import Path
10
- from typing import Any
11
-
12
- import click
13
- from rich.console import Console
14
-
15
- from glaip_sdk.branding import (
16
- ACCENT_STYLE,
17
- ERROR_STYLE,
18
- INFO,
19
- SUCCESS_STYLE,
20
- WARNING_STYLE,
21
- )
22
- from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
23
- from glaip_sdk.cli.display import (
24
- display_api_error,
25
- display_confirmation_prompt,
26
- display_creation_success,
27
- display_deletion_success,
28
- display_update_success,
29
- handle_json_output,
30
- handle_rich_output,
31
- )
32
- from glaip_sdk.cli.io import (
33
- export_resource_to_file_with_validation as export_resource_to_file,
34
- )
35
- from glaip_sdk.cli.io import (
36
- fetch_raw_resource_details,
37
- )
38
- from glaip_sdk.cli.io import (
39
- load_resource_from_file_with_validation as load_resource_from_file,
40
- )
41
- from glaip_sdk.cli.resolution import resolve_resource_reference
42
- from glaip_sdk.cli.rich_helpers import markup_text, print_markup
43
- from glaip_sdk.cli.utils import (
44
- coerce_to_row,
45
- get_client,
46
- output_list,
47
- output_result,
48
- spinner_context,
49
- )
50
- from glaip_sdk.icons import ICON_TOOL
51
- from glaip_sdk.utils import format_datetime
52
- from glaip_sdk.utils.import_export import merge_import_with_cli_args
53
-
54
- console = Console()
55
-
56
-
57
- @click.group(name="tools", no_args_is_help=True)
58
- def tools_group() -> None:
59
- """Tool management operations."""
60
- pass
61
-
62
-
63
- def _resolve_tool(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
64
- """Resolve tool reference (ID or name) with ambiguity handling."""
65
- return resolve_resource_reference(
66
- ctx,
67
- client,
68
- ref,
69
- "tool",
70
- client.get_tool,
71
- client.find_tools,
72
- "Tool",
73
- select=select,
74
- )
75
-
76
-
77
- # ----------------------------- Helpers --------------------------------- #
78
-
79
-
80
- def _extract_internal_name(code: str) -> str:
81
- """Extract plugin class name attribute from tool code."""
82
- m = re.search(r'^\s*name\s*:\s*str\s*=\s*"([^"]+)"', code, re.M)
83
- if not m:
84
- m = re.search(r'^\s*name\s*=\s*"([^"]+)"', code, re.M)
85
- if not m:
86
- raise click.ClickException(
87
- "Could not find plugin 'name' attribute in the tool file. "
88
- 'Ensure your plugin class defines e.g. name: str = "my_tool".'
89
- )
90
- return m.group(1)
91
-
92
-
93
- def _validate_name_match(provided: str | None, internal: str) -> str:
94
- """Validate provided --name against internal name; return effective name."""
95
- if provided and provided != internal:
96
- raise click.ClickException(
97
- f"--name '{provided}' does not match plugin internal name '{internal}'. "
98
- "Either update the code or pass a matching --name."
99
- )
100
- return provided or internal
101
-
102
-
103
- def _check_duplicate_name(client: Any, tool_name: str) -> None:
104
- """Raise if a tool with the same name already exists."""
105
- try:
106
- existing = client.find_tools(name=tool_name)
107
- if existing:
108
- raise click.ClickException(
109
- f"A tool named '{tool_name}' already exists. "
110
- "Please change your plugin's 'name' to a unique value, then re-run."
111
- )
112
- except click.ClickException:
113
- # Re-raise ClickException (intended error)
114
- raise
115
- except Exception:
116
- # Non-fatal: best-effort duplicate check for other errors
117
- pass
118
-
119
-
120
- def _parse_tags(tags: str | None) -> list[str]:
121
- return [t.strip() for t in (tags.split(",") if tags else []) if t.strip()]
122
-
123
-
124
- def _handle_import_file(
125
- import_file: str | None,
126
- name: str | None,
127
- description: str | None,
128
- tags: tuple[str, ...] | None,
129
- ) -> dict[str, Any]:
130
- """Handle import file logic and merge with CLI arguments."""
131
- if import_file:
132
- import_data = load_resource_from_file(Path(import_file), "tool")
133
-
134
- # Merge CLI args with imported data
135
- cli_args = {
136
- "name": name,
137
- "description": description,
138
- "tags": tags,
139
- }
140
-
141
- return merge_import_with_cli_args(import_data, cli_args)
142
- else:
143
- # No import file - use CLI args directly
144
- return {
145
- "name": name,
146
- "description": description,
147
- "tags": tags,
148
- }
149
-
150
-
151
- def _create_tool_from_file(
152
- client: Any,
153
- file_path: str,
154
- name: str | None,
155
- description: str | None,
156
- tags: str | None,
157
- ) -> Any:
158
- """Create tool from file upload."""
159
- with open(file_path, encoding="utf-8") as f:
160
- code_content = f.read()
161
-
162
- internal_name = _extract_internal_name(code_content)
163
- tool_name = _validate_name_match(name, internal_name)
164
- _check_duplicate_name(client, tool_name)
165
-
166
- # Upload the plugin code as-is (no rewrite)
167
- return client.create_tool_from_code(
168
- name=tool_name,
169
- code=code_content,
170
- framework="langchain", # Always langchain
171
- description=description,
172
- tags=_parse_tags(tags) if tags else None,
173
- )
174
-
175
-
176
- def _validate_creation_parameters(
177
- file: str | None,
178
- import_file: str | None,
179
- ) -> None:
180
- """Validate required parameters for tool creation."""
181
- if not file and not import_file:
182
- raise click.ClickException("A tool file must be provided. Use --file to specify the tool file to upload.")
183
-
184
-
185
- @tools_group.command(name="list")
186
- @output_flags()
187
- @click.option(
188
- "--type",
189
- "tool_type",
190
- help="Filter tools by type (e.g., custom, native)",
191
- type=str,
192
- required=False,
193
- )
194
- @click.pass_context
195
- def list_tools(ctx: Any, tool_type: str | None) -> None:
196
- """List all tools."""
197
- try:
198
- client = get_client(ctx)
199
- with spinner_context(
200
- ctx,
201
- "[bold blue]Fetching tools…[/bold blue]",
202
- console_override=console,
203
- ):
204
- tools = client.list_tools(tool_type=tool_type)
205
-
206
- # Define table columns: (data_key, header, style, width)
207
- columns = [
208
- ("id", "ID", "dim", 36),
209
- ("name", "Name", ACCENT_STYLE, None),
210
- ("framework", "Framework", INFO, None),
211
- ]
212
-
213
- # Transform function for safe dictionary access
214
- def transform_tool(tool: Any) -> dict[str, Any]:
215
- row = coerce_to_row(tool, ["id", "name", "framework"])
216
- # Ensure id is always a string
217
- row["id"] = str(row["id"])
218
- return row
219
-
220
- output_list(ctx, tools, f"{ICON_TOOL} Available Tools", columns, transform_tool)
221
-
222
- except Exception as e:
223
- raise click.ClickException(str(e)) from e
224
-
225
-
226
- @tools_group.command()
227
- @click.argument("file_arg", required=False, type=click.Path(exists=True))
228
- @click.option(
229
- "--file",
230
- type=click.Path(exists=True),
231
- help="Tool file to upload",
232
- )
233
- @click.option(
234
- "--name",
235
- help="Tool name (extracted from script if file provided)",
236
- )
237
- @click.option(
238
- "--description",
239
- help="Tool description (extracted from script if file provided)",
240
- )
241
- @click.option(
242
- "--tags",
243
- help="Comma-separated tags for the tool",
244
- )
245
- @click.option(
246
- "--import",
247
- "import_file",
248
- type=click.Path(exists=True, dir_okay=False),
249
- help="Import tool configuration from JSON file",
250
- )
251
- @output_flags()
252
- @click.pass_context
253
- def create(
254
- ctx: Any,
255
- file_arg: str | None,
256
- file: str | None,
257
- name: str | None,
258
- description: str | None,
259
- tags: tuple[str, ...] | None,
260
- import_file: str | None,
261
- ) -> None:
262
- """Create a new tool.
263
-
264
- Examples:
265
- aip tools create tool.py # Create from file
266
- aip tools create --import tool.json # Create from exported configuration
267
- """
268
- try:
269
- client = get_client(ctx)
270
-
271
- # Allow positional file argument for better DX (matches examples)
272
- if not file and file_arg:
273
- file = file_arg
274
-
275
- # Handle import file and merge with CLI arguments
276
- merged_data = _handle_import_file(import_file, name, description, tags)
277
-
278
- # Extract merged values
279
- name = merged_data.get("name")
280
- description = merged_data.get("description")
281
- tags = merged_data.get("tags")
282
-
283
- # Validate required parameters
284
- _validate_creation_parameters(file, import_file)
285
-
286
- # Create tool from file (either direct file or import file)
287
- with spinner_context(
288
- ctx,
289
- "[bold blue]Creating tool…[/bold blue]",
290
- console_override=console,
291
- ):
292
- tool = _create_tool_from_file(client, file, name, description, tags)
293
-
294
- # Handle JSON output
295
- handle_json_output(ctx, tool.model_dump())
296
-
297
- # Handle Rich output
298
- creation_method = "file upload (custom)"
299
- rich_panel = display_creation_success(
300
- "Tool",
301
- tool.name,
302
- tool.id,
303
- Framework=getattr(tool, "framework", "N/A"),
304
- Type=getattr(tool, "tool_type", "N/A"),
305
- Description=getattr(tool, "description", "No description"),
306
- Method=creation_method,
307
- )
308
- handle_rich_output(ctx, rich_panel)
309
-
310
- except Exception as e:
311
- handle_json_output(ctx, error=e)
312
- if get_ctx_value(ctx, "view") != "json":
313
- display_api_error(e, "tool creation")
314
- raise click.ClickException(str(e)) from e
315
-
316
-
317
- @tools_group.command()
318
- @click.argument("tool_ref")
319
- @click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
320
- @click.option(
321
- "--export",
322
- type=click.Path(dir_okay=False, writable=True),
323
- help="Export complete tool configuration to file (format auto-detected from .json/.yaml extension)",
324
- )
325
- @output_flags()
326
- @click.pass_context
327
- def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None:
328
- """Get tool details.
329
-
330
- Examples:
331
- aip tools get my-tool
332
- aip tools get my-tool --export tool.json # Exports complete configuration as JSON
333
- aip tools get my-tool --export tool.yaml # Exports complete configuration as YAML
334
- """
335
- try:
336
- client = get_client(ctx)
337
-
338
- # Resolve tool with ambiguity handling
339
- tool = _resolve_tool(ctx, client, tool_ref, select)
340
-
341
- # Handle export option
342
- if export:
343
- export_path = Path(export)
344
- # Auto-detect format from file extension
345
- detected_format = detect_export_format(export_path)
346
-
347
- # Always export comprehensive data - re-fetch tool with full details if needed
348
- try:
349
- with spinner_context(
350
- ctx,
351
- "[bold blue]Fetching complete tool details…[/bold blue]",
352
- console_override=console,
353
- ):
354
- tool = client.get_tool_by_id(tool.id)
355
- except Exception as e:
356
- print_markup(
357
- f"[{WARNING_STYLE}]⚠️ Could not fetch full tool details: {e}[/]",
358
- console=console,
359
- )
360
- print_markup(
361
- f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]",
362
- console=console,
363
- )
364
-
365
- with spinner_context(
366
- ctx,
367
- "[bold blue]Exporting tool configuration…[/bold blue]",
368
- console_override=console,
369
- ):
370
- export_resource_to_file(tool, export_path, detected_format)
371
- print_markup(
372
- f"[{SUCCESS_STYLE}]✅ Complete tool configuration exported to: {export_path} "
373
- f"(format: {detected_format})[/]",
374
- console=console,
375
- )
376
-
377
- # Try to fetch raw API data first to preserve ALL fields
378
- with spinner_context(
379
- ctx,
380
- "[bold blue]Fetching detailed tool data…[/bold blue]",
381
- console_override=console,
382
- ):
383
- raw_tool_data = fetch_raw_resource_details(client, tool, "tools")
384
-
385
- if raw_tool_data:
386
- # Use raw API data - this preserves ALL fields
387
- # Format dates for better display (minimal postprocessing)
388
- formatted_data = raw_tool_data.copy()
389
- if "created_at" in formatted_data:
390
- formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
391
- if "updated_at" in formatted_data:
392
- formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
393
-
394
- # Display using output_result with raw data
395
- output_result(
396
- ctx,
397
- formatted_data,
398
- title="Tool Details",
399
- panel_title=f"{ICON_TOOL} {raw_tool_data.get('name', 'Unknown')}",
400
- )
401
- else:
402
- # Fall back to original method if raw fetch fails
403
- console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
404
-
405
- # Create result data with all available fields from backend
406
- result_data = {
407
- "id": str(getattr(tool, "id", "N/A")),
408
- "name": getattr(tool, "name", "N/A"),
409
- "tool_type": getattr(tool, "tool_type", "N/A"),
410
- "framework": getattr(tool, "framework", "N/A"),
411
- "version": getattr(tool, "version", "N/A"),
412
- "description": getattr(tool, "description", "N/A"),
413
- }
414
-
415
- output_result(
416
- ctx,
417
- result_data,
418
- title="Tool Details",
419
- panel_title=f"{ICON_TOOL} {tool.name}",
420
- )
421
-
422
- except Exception as e:
423
- raise click.ClickException(str(e)) from e
424
-
425
-
426
- @tools_group.command()
427
- @click.argument("tool_id")
428
- @click.option(
429
- "--file",
430
- type=click.Path(exists=True),
431
- help="New tool file for code update (custom tools only)",
432
- )
433
- @click.option("--description", help="New description")
434
- @click.option("--tags", help="Comma-separated tags")
435
- @output_flags()
436
- @click.pass_context
437
- def update(
438
- ctx: Any,
439
- tool_id: str,
440
- file: str | None,
441
- description: str | None,
442
- tags: tuple[str, ...] | None,
443
- ) -> None:
444
- """Update a tool (code or metadata)."""
445
- try:
446
- client = get_client(ctx)
447
-
448
- # Get tool by ID (no ambiguity handling needed)
449
- try:
450
- with spinner_context(
451
- ctx,
452
- "[bold blue]Fetching tool…[/bold blue]",
453
- console_override=console,
454
- ):
455
- tool = client.get_tool_by_id(tool_id)
456
- except Exception as e:
457
- raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}") from e
458
-
459
- # Prepare update data
460
- update_data = {}
461
- if description:
462
- update_data["description"] = description
463
- if tags:
464
- update_data["tags"] = [tag.strip() for tag in tags.split(",")]
465
-
466
- if file:
467
- # Update code via file upload (custom tools only)
468
- if tool.tool_type != "custom":
469
- raise click.ClickException(
470
- "File updates are only supported for custom tools. "
471
- f"Tool '{tool.name}' is of type '{tool.tool_type}'."
472
- )
473
- with spinner_context(
474
- ctx,
475
- "[bold blue]Uploading new tool code…[/bold blue]",
476
- console_override=console,
477
- ):
478
- updated_tool = client.tools.update_tool_via_file(tool.id, file, framework=tool.framework)
479
- handle_rich_output(
480
- ctx,
481
- markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool code updated from {file}"),
482
- )
483
- elif update_data:
484
- # Update metadata only (native tools only)
485
- if tool.tool_type != "native":
486
- raise click.ClickException(
487
- "Metadata updates are only supported for native tools. "
488
- f"Tool '{tool.name}' is of type '{tool.tool_type}'."
489
- )
490
- with spinner_context(
491
- ctx,
492
- "[bold blue]Updating tool metadata…[/bold blue]",
493
- console_override=console,
494
- ):
495
- updated_tool = tool.update(**update_data)
496
- handle_rich_output(ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated"))
497
- else:
498
- handle_rich_output(ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]"))
499
- return
500
-
501
- handle_json_output(ctx, updated_tool.model_dump())
502
- handle_rich_output(ctx, display_update_success("Tool", updated_tool.name))
503
-
504
- except Exception as e:
505
- handle_json_output(ctx, error=e)
506
- if get_ctx_value(ctx, "view") != "json":
507
- display_api_error(e, "tool update")
508
- raise click.ClickException(str(e)) from e
509
-
510
-
511
- @tools_group.command()
512
- @click.argument("tool_id")
513
- @click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
514
- @output_flags()
515
- @click.pass_context
516
- def delete(ctx: Any, tool_id: str, yes: bool) -> None:
517
- """Delete a tool."""
518
- try:
519
- client = get_client(ctx)
520
-
521
- # Get tool by ID (no ambiguity handling needed)
522
- try:
523
- with spinner_context(
524
- ctx,
525
- "[bold blue]Fetching tool…[/bold blue]",
526
- console_override=console,
527
- ):
528
- tool = client.get_tool_by_id(tool_id)
529
- except Exception as e:
530
- raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}") from e
531
-
532
- # Confirm deletion via centralized display helper
533
- if not yes and not display_confirmation_prompt("Tool", tool.name):
534
- return
535
-
536
- with spinner_context(
537
- ctx,
538
- "[bold blue]Deleting tool…[/bold blue]",
539
- console_override=console,
540
- ):
541
- tool.delete()
542
-
543
- handle_json_output(
544
- ctx,
545
- {
546
- "success": True,
547
- "message": f"Tool '{tool.name}' deleted",
548
- },
549
- )
550
- handle_rich_output(ctx, display_deletion_success("Tool", tool.name))
551
-
552
- except Exception as e:
553
- handle_json_output(ctx, error=e)
554
- if get_ctx_value(ctx, "view") != "json":
555
- display_api_error(e, "tool deletion")
556
- raise click.ClickException(str(e)) from e
557
-
558
-
559
- @tools_group.command("script")
560
- @click.argument("tool_id")
561
- @output_flags()
562
- @click.pass_context
563
- def script(ctx: Any, tool_id: str) -> None:
564
- """Get tool script content."""
565
- try:
566
- client = get_client(ctx)
567
- with spinner_context(
568
- ctx,
569
- "[bold blue]Fetching tool script…[/bold blue]",
570
- console_override=console,
571
- ):
572
- script_content = client.get_tool_script(tool_id)
573
-
574
- if get_ctx_value(ctx, "view") == "json":
575
- click.echo(json.dumps({"script": script_content}, indent=2))
576
- else:
577
- console.print(f"[{SUCCESS_STYLE}]📜 Tool Script for '{tool_id}':[/]")
578
- console.print(script_content)
579
-
580
- except Exception as e:
581
- handle_json_output(ctx, error=e)
582
- if get_ctx_value(ctx, "view") != "json":
583
- print_markup(f"[{ERROR_STYLE}]Error getting tool script: {e}[/]", console=console)
584
- raise click.ClickException(str(e)) from e