glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 (161) hide show
  1. glaip_sdk/__init__.py +6 -3
  2. glaip_sdk/_version.py +12 -5
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1126 -0
  5. glaip_sdk/branding.py +79 -15
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +699 -0
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents.py +503 -183
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +774 -137
  14. glaip_sdk/cli/commands/mcps.py +1124 -181
  15. glaip_sdk/cli/commands/models.py +25 -10
  16. glaip_sdk/cli/commands/tools.py +144 -92
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +61 -0
  19. glaip_sdk/cli/config.py +95 -0
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +150 -0
  22. glaip_sdk/cli/core/__init__.py +79 -0
  23. glaip_sdk/cli/core/context.py +124 -0
  24. glaip_sdk/cli/core/output.py +846 -0
  25. glaip_sdk/cli/core/prompting.py +649 -0
  26. glaip_sdk/cli/core/rendering.py +187 -0
  27. glaip_sdk/cli/display.py +143 -53
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +24 -18
  30. glaip_sdk/cli/main.py +420 -145
  31. glaip_sdk/cli/masking.py +136 -0
  32. glaip_sdk/cli/mcp_validators.py +287 -0
  33. glaip_sdk/cli/pager.py +266 -0
  34. glaip_sdk/cli/parsers/__init__.py +7 -0
  35. glaip_sdk/cli/parsers/json_input.py +177 -0
  36. glaip_sdk/cli/resolution.py +28 -21
  37. glaip_sdk/cli/rich_helpers.py +27 -0
  38. glaip_sdk/cli/slash/__init__.py +15 -0
  39. glaip_sdk/cli/slash/accounts_controller.py +500 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +282 -0
  42. glaip_sdk/cli/slash/prompt.py +245 -0
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +1679 -0
  45. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  46. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  47. glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
  48. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  49. glaip_sdk/cli/slash/tui/loading.py +58 -0
  50. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  51. glaip_sdk/cli/transcript/__init__.py +31 -0
  52. glaip_sdk/cli/transcript/cache.py +536 -0
  53. glaip_sdk/cli/transcript/capture.py +329 -0
  54. glaip_sdk/cli/transcript/export.py +38 -0
  55. glaip_sdk/cli/transcript/history.py +815 -0
  56. glaip_sdk/cli/transcript/launcher.py +77 -0
  57. glaip_sdk/cli/transcript/viewer.py +372 -0
  58. glaip_sdk/cli/update_notifier.py +290 -0
  59. glaip_sdk/cli/utils.py +247 -1238
  60. glaip_sdk/cli/validators.py +16 -18
  61. glaip_sdk/client/__init__.py +2 -1
  62. glaip_sdk/client/_agent_payloads.py +520 -0
  63. glaip_sdk/client/agent_runs.py +147 -0
  64. glaip_sdk/client/agents.py +940 -574
  65. glaip_sdk/client/base.py +163 -48
  66. glaip_sdk/client/main.py +35 -12
  67. glaip_sdk/client/mcps.py +126 -18
  68. glaip_sdk/client/run_rendering.py +415 -0
  69. glaip_sdk/client/shared.py +21 -0
  70. glaip_sdk/client/tools.py +195 -37
  71. glaip_sdk/client/validators.py +20 -48
  72. glaip_sdk/config/constants.py +15 -5
  73. glaip_sdk/exceptions.py +16 -9
  74. glaip_sdk/icons.py +25 -0
  75. glaip_sdk/mcps/__init__.py +21 -0
  76. glaip_sdk/mcps/base.py +345 -0
  77. glaip_sdk/models/__init__.py +90 -0
  78. glaip_sdk/models/agent.py +47 -0
  79. glaip_sdk/models/agent_runs.py +116 -0
  80. glaip_sdk/models/common.py +42 -0
  81. glaip_sdk/models/mcp.py +33 -0
  82. glaip_sdk/models/tool.py +33 -0
  83. glaip_sdk/payload_schemas/__init__.py +7 -0
  84. glaip_sdk/payload_schemas/agent.py +85 -0
  85. glaip_sdk/registry/__init__.py +55 -0
  86. glaip_sdk/registry/agent.py +164 -0
  87. glaip_sdk/registry/base.py +139 -0
  88. glaip_sdk/registry/mcp.py +253 -0
  89. glaip_sdk/registry/tool.py +231 -0
  90. glaip_sdk/rich_components.py +98 -2
  91. glaip_sdk/runner/__init__.py +59 -0
  92. glaip_sdk/runner/base.py +84 -0
  93. glaip_sdk/runner/deps.py +115 -0
  94. glaip_sdk/runner/langgraph.py +597 -0
  95. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  99. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
  102. glaip_sdk/tools/__init__.py +22 -0
  103. glaip_sdk/tools/base.py +435 -0
  104. glaip_sdk/utils/__init__.py +59 -13
  105. glaip_sdk/utils/a2a/__init__.py +34 -0
  106. glaip_sdk/utils/a2a/event_processor.py +188 -0
  107. glaip_sdk/utils/agent_config.py +53 -40
  108. glaip_sdk/utils/bundler.py +267 -0
  109. glaip_sdk/utils/client.py +111 -0
  110. glaip_sdk/utils/client_utils.py +58 -26
  111. glaip_sdk/utils/datetime_helpers.py +58 -0
  112. glaip_sdk/utils/discovery.py +78 -0
  113. glaip_sdk/utils/display.py +65 -32
  114. glaip_sdk/utils/export.py +143 -0
  115. glaip_sdk/utils/general.py +1 -36
  116. glaip_sdk/utils/import_export.py +20 -25
  117. glaip_sdk/utils/import_resolver.py +492 -0
  118. glaip_sdk/utils/instructions.py +101 -0
  119. glaip_sdk/utils/rendering/__init__.py +115 -1
  120. glaip_sdk/utils/rendering/formatting.py +85 -43
  121. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  122. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
  123. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  124. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  125. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  126. glaip_sdk/utils/rendering/models.py +39 -7
  127. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  128. glaip_sdk/utils/rendering/renderer/base.py +672 -759
  129. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  130. glaip_sdk/utils/rendering/renderer/debug.py +75 -22
  131. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  132. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  133. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  134. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  135. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  136. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  137. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  138. glaip_sdk/utils/rendering/state.py +204 -0
  139. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  140. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  141. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  142. glaip_sdk/utils/rendering/steps/format.py +176 -0
  143. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  144. glaip_sdk/utils/rendering/timing.py +36 -0
  145. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  146. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  147. glaip_sdk/utils/resource_refs.py +29 -26
  148. glaip_sdk/utils/runtime_config.py +422 -0
  149. glaip_sdk/utils/serialization.py +184 -51
  150. glaip_sdk/utils/sync.py +142 -0
  151. glaip_sdk/utils/tool_detection.py +33 -0
  152. glaip_sdk/utils/validation.py +21 -30
  153. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
  154. glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
  155. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
  156. glaip_sdk/models.py +0 -250
  157. glaip_sdk/utils/rendering/renderer/progress.py +0 -118
  158. glaip_sdk/utils/rendering/steps.py +0 -232
  159. glaip_sdk/utils/rich_utils.py +0 -29
  160. glaip_sdk-0.0.7.dist-info/RECORD +0 -55
  161. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
@@ -9,7 +9,12 @@ from typing import Any
9
9
  import click
10
10
  from rich.console import Console
11
11
 
12
- from glaip_sdk.cli.utils import get_client, output_flags, output_list
12
+ from glaip_sdk.branding import ACCENT_STYLE, INFO, SUCCESS
13
+ from glaip_sdk.cli.context import output_flags
14
+ from glaip_sdk.cli.utils import (
15
+ output_list,
16
+ with_client_and_spinner,
17
+ )
13
18
 
14
19
  console = Console()
15
20
 
@@ -26,19 +31,31 @@ def models_group() -> None:
26
31
  def list_models(ctx: Any) -> None:
27
32
  """List available language models."""
28
33
  try:
29
- client = get_client(ctx)
30
- models = client.list_language_models()
34
+ with with_client_and_spinner(
35
+ ctx,
36
+ "[bold blue]Fetching language models…[/bold blue]",
37
+ console_override=console,
38
+ ) as client:
39
+ models = client.list_language_models()
31
40
 
32
41
  # Define table columns: (data_key, header, style, width)
33
42
  columns = [
34
43
  ("id", "ID", "dim", 36),
35
- ("provider", "Provider", "cyan", None),
36
- ("name", "Model", "green", None),
37
- ("base_url", "Base URL", "yellow", None),
44
+ ("provider", "Provider", ACCENT_STYLE, None),
45
+ ("name", "Model", SUCCESS, None),
46
+ ("base_url", "Base URL", INFO, None),
38
47
  ]
39
48
 
40
49
  # Transform function for safe dictionary access
41
50
  def transform_model(model: dict[str, Any]) -> dict[str, Any]:
51
+ """Transform a model dictionary to a display row dictionary.
52
+
53
+ Args:
54
+ model: Model dictionary to transform.
55
+
56
+ Returns:
57
+ Dictionary with id, provider, name, and base_url fields.
58
+ """
42
59
  return {
43
60
  "id": str(model.get("id", "N/A")),
44
61
  "provider": model.get("provider", "N/A"),
@@ -46,9 +63,7 @@ def list_models(ctx: Any) -> None:
46
63
  "base_url": model.get("base_url", "Default") or "Default",
47
64
  }
48
65
 
49
- output_list(
50
- ctx, models, "🧠 Available Language Models", columns, transform_model
51
- )
66
+ output_list(ctx, models, "🧠 Available Language Models", columns, transform_model)
52
67
 
53
68
  except Exception as e:
54
- raise click.ClickException(str(e))
69
+ raise click.ClickException(str(e)) from e
@@ -10,9 +10,14 @@ from pathlib import Path
10
10
  from typing import Any
11
11
 
12
12
  import click
13
- from rich.console import Console
14
- from rich.text import Text
15
-
13
+ from glaip_sdk.branding import (
14
+ ACCENT_STYLE,
15
+ ERROR_STYLE,
16
+ INFO,
17
+ SUCCESS_STYLE,
18
+ WARNING_STYLE,
19
+ )
20
+ from glaip_sdk.cli.context import get_ctx_value, output_flags
16
21
  from glaip_sdk.cli.display import (
17
22
  display_api_error,
18
23
  display_confirmation_prompt,
@@ -22,27 +27,25 @@ from glaip_sdk.cli.display import (
22
27
  handle_json_output,
23
28
  handle_rich_output,
24
29
  )
25
- from glaip_sdk.cli.io import (
26
- export_resource_to_file_with_validation as export_resource_to_file,
27
- )
28
- from glaip_sdk.cli.io import (
29
- fetch_raw_resource_details,
30
- )
30
+ from glaip_sdk.cli.io import fetch_raw_resource_details
31
31
  from glaip_sdk.cli.io import (
32
32
  load_resource_from_file_with_validation as load_resource_from_file,
33
33
  )
34
34
  from glaip_sdk.cli.resolution import resolve_resource_reference
35
+ from glaip_sdk.cli.rich_helpers import markup_text, print_markup
35
36
  from glaip_sdk.cli.utils import (
36
37
  coerce_to_row,
37
- detect_export_format,
38
+ format_datetime_fields,
38
39
  get_client,
39
- get_ctx_value,
40
- output_flags,
40
+ handle_best_effort_check,
41
+ handle_resource_export,
41
42
  output_list,
42
43
  output_result,
44
+ spinner_context,
43
45
  )
44
- from glaip_sdk.utils import format_datetime
46
+ from glaip_sdk.icons import ICON_TOOL
45
47
  from glaip_sdk.utils.import_export import merge_import_with_cli_args
48
+ from rich.console import Console
46
49
 
47
50
  console = Console()
48
51
 
@@ -53,17 +56,32 @@ def tools_group() -> None:
53
56
  pass
54
57
 
55
58
 
56
- def _resolve_tool(
57
- ctx: Any, client: Any, ref: str, select: int | None = None
58
- ) -> Any | None:
59
- """Resolve tool reference (ID or name) with ambiguity handling."""
59
+ def _resolve_tool(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
60
+ """Resolve a tool by ID or name, handling ambiguous matches interactively.
61
+
62
+ This function provides tool-specific resolution logic. It uses
63
+ resolve_resource_reference to find tools by UUID or name, with interactive
64
+ selection when multiple matches are found.
65
+
66
+ Args:
67
+ ctx: Click context for CLI operations.
68
+ client: API client instance.
69
+ ref: Tool reference (UUID string or name).
70
+ select: Pre-selected index for non-interactive mode (1-based).
71
+
72
+ Returns:
73
+ Tool object if found, None otherwise.
74
+ """
75
+ # Configure tool-specific resolution with standard fuzzy matching
76
+ get_by_id = client.get_tool
77
+ find_by_name = client.find_tools
60
78
  return resolve_resource_reference(
61
79
  ctx,
62
80
  client,
63
81
  ref,
64
82
  "tool",
65
- client.get_tool,
66
- client.find_tools,
83
+ get_by_id,
84
+ find_by_name,
67
85
  "Tool",
68
86
  select=select,
69
87
  )
@@ -97,22 +115,20 @@ def _validate_name_match(provided: str | None, internal: str) -> str:
97
115
 
98
116
  def _check_duplicate_name(client: Any, tool_name: str) -> None:
99
117
  """Raise if a tool with the same name already exists."""
100
- try:
118
+
119
+ def _check_duplicate() -> None:
101
120
  existing = client.find_tools(name=tool_name)
102
121
  if existing:
103
122
  raise click.ClickException(
104
123
  f"A tool named '{tool_name}' already exists. "
105
124
  "Please change your plugin's 'name' to a unique value, then re-run."
106
125
  )
107
- except click.ClickException:
108
- # Re-raise ClickException (intended error)
109
- raise
110
- except Exception:
111
- # Non-fatal: best-effort duplicate check for other errors
112
- pass
126
+
127
+ handle_best_effort_check(_check_duplicate)
113
128
 
114
129
 
115
130
  def _parse_tags(tags: str | None) -> list[str]:
131
+ """Return a cleaned list of tag strings from a comma-separated input."""
116
132
  return [t.strip() for t in (tags.split(",") if tags else []) if t.strip()]
117
133
 
118
134
 
@@ -174,9 +190,7 @@ def _validate_creation_parameters(
174
190
  ) -> None:
175
191
  """Validate required parameters for tool creation."""
176
192
  if not file and not import_file:
177
- raise click.ClickException(
178
- "A tool file must be provided. Use --file to specify the tool file to upload."
179
- )
193
+ raise click.ClickException("A tool file must be provided. Use --file to specify the tool file to upload.")
180
194
 
181
195
 
182
196
  @tools_group.command(name="list")
@@ -193,26 +207,39 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
193
207
  """List all tools."""
194
208
  try:
195
209
  client = get_client(ctx)
196
- tools = client.list_tools(tool_type=tool_type)
210
+ with spinner_context(
211
+ ctx,
212
+ "[bold blue]Fetching tools…[/bold blue]",
213
+ console_override=console,
214
+ ):
215
+ tools = client.list_tools(tool_type=tool_type)
197
216
 
198
217
  # Define table columns: (data_key, header, style, width)
199
218
  columns = [
200
219
  ("id", "ID", "dim", 36),
201
- ("name", "Name", "cyan", None),
202
- ("framework", "Framework", "blue", None),
220
+ ("name", "Name", ACCENT_STYLE, None),
221
+ ("framework", "Framework", INFO, None),
203
222
  ]
204
223
 
205
224
  # Transform function for safe dictionary access
206
225
  def transform_tool(tool: Any) -> dict[str, Any]:
226
+ """Transform a tool object to a display row dictionary.
227
+
228
+ Args:
229
+ tool: Tool object to transform.
230
+
231
+ Returns:
232
+ Dictionary with id, name, and framework fields.
233
+ """
207
234
  row = coerce_to_row(tool, ["id", "name", "framework"])
208
235
  # Ensure id is always a string
209
236
  row["id"] = str(row["id"])
210
237
  return row
211
238
 
212
- output_list(ctx, tools, "🔧 Available Tools", columns, transform_tool)
239
+ output_list(ctx, tools, f"{ICON_TOOL} Available Tools", columns, transform_tool)
213
240
 
214
241
  except Exception as e:
215
- raise click.ClickException(str(e))
242
+ raise click.ClickException(str(e)) from e
216
243
 
217
244
 
218
245
  @tools_group.command()
@@ -251,8 +278,9 @@ def create(
251
278
  tags: tuple[str, ...] | None,
252
279
  import_file: str | None,
253
280
  ) -> None:
254
- """Create a new tool.
281
+ r"""Create a new tool.
255
282
 
283
+ \b
256
284
  Examples:
257
285
  aip tools create tool.py # Create from file
258
286
  aip tools create --import tool.json # Create from exported configuration
@@ -276,7 +304,12 @@ def create(
276
304
  _validate_creation_parameters(file, import_file)
277
305
 
278
306
  # Create tool from file (either direct file or import file)
279
- tool = _create_tool_from_file(client, file, name, description, tags)
307
+ with spinner_context(
308
+ ctx,
309
+ "[bold blue]Creating tool…[/bold blue]",
310
+ console_override=console,
311
+ ):
312
+ tool = _create_tool_from_file(client, file, name, description, tags)
280
313
 
281
314
  # Handle JSON output
282
315
  handle_json_output(ctx, tool.model_dump())
@@ -298,7 +331,7 @@ def create(
298
331
  handle_json_output(ctx, error=e)
299
332
  if get_ctx_value(ctx, "view") != "json":
300
333
  display_api_error(e, "tool creation")
301
- raise click.ClickException(str(e))
334
+ raise click.ClickException(str(e)) from e
302
335
 
303
336
 
304
337
  @tools_group.command()
@@ -312,8 +345,9 @@ def create(
312
345
  @output_flags()
313
346
  @click.pass_context
314
347
  def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None:
315
- """Get tool details.
348
+ r"""Get tool details.
316
349
 
350
+ \b
317
351
  Examples:
318
352
  aip tools get my-tool
319
353
  aip tools get my-tool --export tool.json # Exports complete configuration as JSON
@@ -327,54 +361,38 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
327
361
 
328
362
  # Handle export option
329
363
  if export:
330
- export_path = Path(export)
331
- # Auto-detect format from file extension
332
- detected_format = detect_export_format(export_path)
333
-
334
- # Always export comprehensive data - re-fetch tool with full details if needed
335
- try:
336
- tool = client.get_tool_by_id(tool.id)
337
- except Exception as e:
338
- console.print(
339
- Text(f"[yellow]⚠️ Could not fetch full tool details: {e}[/yellow]")
340
- )
341
- console.print(
342
- Text("[yellow]⚠️ Proceeding with available data[/yellow]")
343
- )
344
-
345
- export_resource_to_file(tool, export_path, detected_format)
346
- console.print(
347
- Text(
348
- f"[green]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/green]"
349
- )
364
+ handle_resource_export(
365
+ ctx,
366
+ tool,
367
+ Path(export),
368
+ resource_type="tool",
369
+ get_by_id_func=client.get_tool_by_id,
370
+ console_override=console,
350
371
  )
351
372
 
352
373
  # Try to fetch raw API data first to preserve ALL fields
353
- raw_tool_data = fetch_raw_resource_details(client, tool, "tools")
374
+ with spinner_context(
375
+ ctx,
376
+ "[bold blue]Fetching detailed tool data…[/bold blue]",
377
+ console_override=console,
378
+ ):
379
+ raw_tool_data = fetch_raw_resource_details(client, tool, "tools")
354
380
 
355
381
  if raw_tool_data:
356
382
  # Use raw API data - this preserves ALL fields
357
383
  # Format dates for better display (minimal postprocessing)
358
- formatted_data = raw_tool_data.copy()
359
- if "created_at" in formatted_data:
360
- formatted_data["created_at"] = format_datetime(
361
- formatted_data["created_at"]
362
- )
363
- if "updated_at" in formatted_data:
364
- formatted_data["updated_at"] = format_datetime(
365
- formatted_data["updated_at"]
366
- )
384
+ formatted_data = format_datetime_fields(raw_tool_data)
367
385
 
368
386
  # Display using output_result with raw data
369
387
  output_result(
370
388
  ctx,
371
389
  formatted_data,
372
390
  title="Tool Details",
373
- panel_title=f"🔧 {raw_tool_data.get('name', 'Unknown')}",
391
+ panel_title=f"{ICON_TOOL} {raw_tool_data.get('name', 'Unknown')}",
374
392
  )
375
393
  else:
376
394
  # Fall back to original method if raw fetch fails
377
- console.print("[yellow]Falling back to Pydantic model data[/yellow]")
395
+ console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
378
396
 
379
397
  # Create result data with all available fields from backend
380
398
  result_data = {
@@ -387,11 +405,14 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
387
405
  }
388
406
 
389
407
  output_result(
390
- ctx, result_data, title="Tool Details", panel_title=f"🔧 {tool.name}"
408
+ ctx,
409
+ result_data,
410
+ title="Tool Details",
411
+ panel_title=f"{ICON_TOOL} {tool.name}",
391
412
  )
392
413
 
393
414
  except Exception as e:
394
- raise click.ClickException(str(e))
415
+ raise click.ClickException(str(e)) from e
395
416
 
396
417
 
397
418
  @tools_group.command()
@@ -418,9 +439,14 @@ def update(
418
439
 
419
440
  # Get tool by ID (no ambiguity handling needed)
420
441
  try:
421
- tool = client.get_tool_by_id(tool_id)
442
+ with spinner_context(
443
+ ctx,
444
+ "[bold blue]Fetching tool…[/bold blue]",
445
+ console_override=console,
446
+ ):
447
+ tool = client.get_tool_by_id(tool_id)
422
448
  except Exception as e:
423
- raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}")
449
+ raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}") from e
424
450
 
425
451
  # Prepare update data
426
452
  update_data = {}
@@ -433,24 +459,35 @@ def update(
433
459
  # Update code via file upload (custom tools only)
434
460
  if tool.tool_type != "custom":
435
461
  raise click.ClickException(
436
- f"File updates are only supported for custom tools. Tool '{tool.name}' is of type '{tool.tool_type}'."
462
+ "File updates are only supported for custom tools. "
463
+ f"Tool '{tool.name}' is of type '{tool.tool_type}'."
437
464
  )
438
- updated_tool = client.tools.update_tool_via_file(
439
- tool.id, file, framework=tool.framework
440
- )
465
+ with spinner_context(
466
+ ctx,
467
+ "[bold blue]Uploading new tool code…[/bold blue]",
468
+ console_override=console,
469
+ ):
470
+ updated_tool = client.tools.update_tool_via_file(tool.id, file, framework=tool.framework)
441
471
  handle_rich_output(
442
- ctx, Text(f"[green]✓[/green] Tool code updated from {file}")
472
+ ctx,
473
+ markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool code updated from {file}"),
443
474
  )
444
475
  elif update_data:
445
476
  # Update metadata only (native tools only)
446
477
  if tool.tool_type != "native":
447
478
  raise click.ClickException(
448
- f"Metadata updates are only supported for native tools. Tool '{tool.name}' is of type '{tool.tool_type}'."
479
+ "Metadata updates are only supported for native tools. "
480
+ f"Tool '{tool.name}' is of type '{tool.tool_type}'."
449
481
  )
450
- updated_tool = tool.update(**update_data)
451
- handle_rich_output(ctx, Text("[green]✓[/green] Tool metadata updated"))
482
+ with spinner_context(
483
+ ctx,
484
+ "[bold blue]Updating tool metadata…[/bold blue]",
485
+ console_override=console,
486
+ ):
487
+ updated_tool = tool.update(**update_data)
488
+ handle_rich_output(ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated"))
452
489
  else:
453
- handle_rich_output(ctx, Text("[yellow]No updates specified[/yellow]"))
490
+ handle_rich_output(ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]"))
454
491
  return
455
492
 
456
493
  handle_json_output(ctx, updated_tool.model_dump())
@@ -460,7 +497,7 @@ def update(
460
497
  handle_json_output(ctx, error=e)
461
498
  if get_ctx_value(ctx, "view") != "json":
462
499
  display_api_error(e, "tool update")
463
- raise click.ClickException(str(e))
500
+ raise click.ClickException(str(e)) from e
464
501
 
465
502
 
466
503
  @tools_group.command()
@@ -475,15 +512,25 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
475
512
 
476
513
  # Get tool by ID (no ambiguity handling needed)
477
514
  try:
478
- tool = client.get_tool_by_id(tool_id)
515
+ with spinner_context(
516
+ ctx,
517
+ "[bold blue]Fetching tool…[/bold blue]",
518
+ console_override=console,
519
+ ):
520
+ tool = client.get_tool_by_id(tool_id)
479
521
  except Exception as e:
480
- raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}")
522
+ raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}") from e
481
523
 
482
524
  # Confirm deletion via centralized display helper
483
525
  if not yes and not display_confirmation_prompt("Tool", tool.name):
484
526
  return
485
527
 
486
- tool.delete()
528
+ with spinner_context(
529
+ ctx,
530
+ "[bold blue]Deleting tool…[/bold blue]",
531
+ console_override=console,
532
+ ):
533
+ tool.delete()
487
534
 
488
535
  handle_json_output(
489
536
  ctx,
@@ -498,7 +545,7 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
498
545
  handle_json_output(ctx, error=e)
499
546
  if get_ctx_value(ctx, "view") != "json":
500
547
  display_api_error(e, "tool deletion")
501
- raise click.ClickException(str(e))
548
+ raise click.ClickException(str(e)) from e
502
549
 
503
550
 
504
551
  @tools_group.command("script")
@@ -509,16 +556,21 @@ def script(ctx: Any, tool_id: str) -> None:
509
556
  """Get tool script content."""
510
557
  try:
511
558
  client = get_client(ctx)
512
- script_content = client.get_tool_script(tool_id)
559
+ with spinner_context(
560
+ ctx,
561
+ "[bold blue]Fetching tool script…[/bold blue]",
562
+ console_override=console,
563
+ ):
564
+ script_content = client.get_tool_script(tool_id)
513
565
 
514
566
  if get_ctx_value(ctx, "view") == "json":
515
567
  click.echo(json.dumps({"script": script_content}, indent=2))
516
568
  else:
517
- console.print(f"[green]📜 Tool Script for '{tool_id}':[/green]")
569
+ console.print(f"[{SUCCESS_STYLE}]📜 Tool Script for '{tool_id}':[/]")
518
570
  console.print(script_content)
519
571
 
520
572
  except Exception as e:
521
573
  handle_json_output(ctx, error=e)
522
574
  if get_ctx_value(ctx, "view") != "json":
523
- console.print(Text(f"[red]Error getting tool script: {e}[/red]"))
524
- raise click.ClickException(str(e))
575
+ print_markup(f"[{ERROR_STYLE}]Error getting tool script: {e}[/]", console=console)
576
+ raise click.ClickException(str(e)) from e