glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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 (156) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +10 -3
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1191 -0
  5. glaip_sdk/branding.py +15 -6
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +265 -45
  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 +251 -173
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +735 -143
  14. glaip_sdk/cli/commands/mcps.py +266 -134
  15. glaip_sdk/cli/commands/models.py +13 -9
  16. glaip_sdk/cli/commands/tools.py +67 -88
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +3 -8
  19. glaip_sdk/cli/config.py +49 -7
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +8 -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 +45 -32
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +14 -17
  30. glaip_sdk/cli/main.py +232 -143
  31. glaip_sdk/cli/masking.py +21 -33
  32. glaip_sdk/cli/mcp_validators.py +5 -15
  33. glaip_sdk/cli/pager.py +12 -19
  34. glaip_sdk/cli/parsers/__init__.py +1 -3
  35. glaip_sdk/cli/parsers/json_input.py +11 -22
  36. glaip_sdk/cli/resolution.py +3 -9
  37. glaip_sdk/cli/rich_helpers.py +1 -3
  38. glaip_sdk/cli/slash/__init__.py +0 -9
  39. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +65 -29
  42. glaip_sdk/cli/slash/prompt.py +24 -10
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +807 -225
  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 +876 -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 +12 -52
  52. glaip_sdk/cli/transcript/cache.py +258 -60
  53. glaip_sdk/cli/transcript/capture.py +72 -21
  54. glaip_sdk/cli/transcript/history.py +815 -0
  55. glaip_sdk/cli/transcript/launcher.py +1 -3
  56. glaip_sdk/cli/transcript/viewer.py +79 -499
  57. glaip_sdk/cli/update_notifier.py +177 -24
  58. glaip_sdk/cli/utils.py +242 -1308
  59. glaip_sdk/cli/validators.py +16 -18
  60. glaip_sdk/client/__init__.py +2 -1
  61. glaip_sdk/client/_agent_payloads.py +53 -37
  62. glaip_sdk/client/agent_runs.py +147 -0
  63. glaip_sdk/client/agents.py +320 -92
  64. glaip_sdk/client/base.py +78 -35
  65. glaip_sdk/client/main.py +19 -10
  66. glaip_sdk/client/mcps.py +123 -15
  67. glaip_sdk/client/run_rendering.py +136 -101
  68. glaip_sdk/client/shared.py +21 -0
  69. glaip_sdk/client/tools.py +163 -34
  70. glaip_sdk/client/validators.py +20 -48
  71. glaip_sdk/config/constants.py +11 -0
  72. glaip_sdk/exceptions.py +1 -3
  73. glaip_sdk/mcps/__init__.py +21 -0
  74. glaip_sdk/mcps/base.py +345 -0
  75. glaip_sdk/models/__init__.py +90 -0
  76. glaip_sdk/models/agent.py +47 -0
  77. glaip_sdk/models/agent_runs.py +116 -0
  78. glaip_sdk/models/common.py +42 -0
  79. glaip_sdk/models/mcp.py +33 -0
  80. glaip_sdk/models/tool.py +33 -0
  81. glaip_sdk/payload_schemas/__init__.py +1 -13
  82. glaip_sdk/payload_schemas/agent.py +1 -3
  83. glaip_sdk/registry/__init__.py +55 -0
  84. glaip_sdk/registry/agent.py +164 -0
  85. glaip_sdk/registry/base.py +139 -0
  86. glaip_sdk/registry/mcp.py +253 -0
  87. glaip_sdk/registry/tool.py +232 -0
  88. glaip_sdk/rich_components.py +58 -2
  89. glaip_sdk/runner/__init__.py +59 -0
  90. glaip_sdk/runner/base.py +84 -0
  91. glaip_sdk/runner/deps.py +115 -0
  92. glaip_sdk/runner/langgraph.py +706 -0
  93. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  94. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  95. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  96. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  97. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  98. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  99. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  100. glaip_sdk/tools/__init__.py +22 -0
  101. glaip_sdk/tools/base.py +435 -0
  102. glaip_sdk/utils/__init__.py +58 -12
  103. glaip_sdk/utils/a2a/__init__.py +34 -0
  104. glaip_sdk/utils/a2a/event_processor.py +188 -0
  105. glaip_sdk/utils/agent_config.py +4 -14
  106. glaip_sdk/utils/bundler.py +267 -0
  107. glaip_sdk/utils/client.py +111 -0
  108. glaip_sdk/utils/client_utils.py +46 -28
  109. glaip_sdk/utils/datetime_helpers.py +58 -0
  110. glaip_sdk/utils/discovery.py +78 -0
  111. glaip_sdk/utils/display.py +25 -21
  112. glaip_sdk/utils/export.py +143 -0
  113. glaip_sdk/utils/general.py +1 -36
  114. glaip_sdk/utils/import_export.py +15 -16
  115. glaip_sdk/utils/import_resolver.py +492 -0
  116. glaip_sdk/utils/instructions.py +101 -0
  117. glaip_sdk/utils/rendering/__init__.py +115 -1
  118. glaip_sdk/utils/rendering/formatting.py +7 -35
  119. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  120. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
  121. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
  122. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  123. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  124. glaip_sdk/utils/rendering/models.py +3 -6
  125. glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
  126. glaip_sdk/utils/rendering/renderer/base.py +258 -1577
  127. glaip_sdk/utils/rendering/renderer/config.py +1 -5
  128. glaip_sdk/utils/rendering/renderer/debug.py +30 -34
  129. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  130. glaip_sdk/utils/rendering/renderer/stream.py +10 -51
  131. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  132. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  133. glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
  134. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  135. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  136. glaip_sdk/utils/rendering/state.py +204 -0
  137. glaip_sdk/utils/rendering/step_tree_state.py +1 -3
  138. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  139. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
  140. glaip_sdk/utils/rendering/steps/format.py +176 -0
  141. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  142. glaip_sdk/utils/rendering/timing.py +36 -0
  143. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  144. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  145. glaip_sdk/utils/resource_refs.py +29 -26
  146. glaip_sdk/utils/runtime_config.py +425 -0
  147. glaip_sdk/utils/serialization.py +32 -46
  148. glaip_sdk/utils/sync.py +142 -0
  149. glaip_sdk/utils/tool_detection.py +33 -0
  150. glaip_sdk/utils/validation.py +20 -28
  151. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
  152. glaip_sdk-0.6.10.dist-info/RECORD +159 -0
  153. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
  154. glaip_sdk/models.py +0 -259
  155. glaip_sdk-0.1.0.dist-info/RECORD +0 -82
  156. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
@@ -12,9 +12,8 @@ from rich.console import Console
12
12
  from glaip_sdk.branding import ACCENT_STYLE, INFO, SUCCESS
13
13
  from glaip_sdk.cli.context import output_flags
14
14
  from glaip_sdk.cli.utils import (
15
- get_client,
16
15
  output_list,
17
- spinner_context,
16
+ with_client_and_spinner,
18
17
  )
19
18
 
20
19
  console = Console()
@@ -32,12 +31,11 @@ def models_group() -> None:
32
31
  def list_models(ctx: Any) -> None:
33
32
  """List available language models."""
34
33
  try:
35
- client = get_client(ctx)
36
- with spinner_context(
34
+ with with_client_and_spinner(
37
35
  ctx,
38
36
  "[bold blue]Fetching language models…[/bold blue]",
39
37
  console_override=console,
40
- ):
38
+ ) as client:
41
39
  models = client.list_language_models()
42
40
 
43
41
  # Define table columns: (data_key, header, style, width)
@@ -50,6 +48,14 @@ def list_models(ctx: Any) -> None:
50
48
 
51
49
  # Transform function for safe dictionary access
52
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
+ """
53
59
  return {
54
60
  "id": str(model.get("id", "N/A")),
55
61
  "provider": model.get("provider", "N/A"),
@@ -57,9 +63,7 @@ def list_models(ctx: Any) -> None:
57
63
  "base_url": model.get("base_url", "Default") or "Default",
58
64
  }
59
65
 
60
- output_list(
61
- ctx, models, "🧠 Available Language Models", columns, transform_model
62
- )
66
+ output_list(ctx, models, "🧠 Available Language Models", columns, transform_model)
63
67
 
64
68
  except Exception as e:
65
- raise click.ClickException(str(e))
69
+ raise click.ClickException(str(e)) from e
@@ -10,8 +10,6 @@ from pathlib import Path
10
10
  from typing import Any
11
11
 
12
12
  import click
13
- from rich.console import Console
14
-
15
13
  from glaip_sdk.branding import (
16
14
  ACCENT_STYLE,
17
15
  ERROR_STYLE,
@@ -19,7 +17,7 @@ from glaip_sdk.branding import (
19
17
  SUCCESS_STYLE,
20
18
  WARNING_STYLE,
21
19
  )
22
- from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
20
+ from glaip_sdk.cli.context import get_ctx_value, output_flags
23
21
  from glaip_sdk.cli.display import (
24
22
  display_api_error,
25
23
  display_confirmation_prompt,
@@ -29,12 +27,7 @@ from glaip_sdk.cli.display import (
29
27
  handle_json_output,
30
28
  handle_rich_output,
31
29
  )
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
- )
30
+ from glaip_sdk.cli.io import fetch_raw_resource_details
38
31
  from glaip_sdk.cli.io import (
39
32
  load_resource_from_file_with_validation as load_resource_from_file,
40
33
  )
@@ -42,14 +35,17 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
42
35
  from glaip_sdk.cli.rich_helpers import markup_text, print_markup
43
36
  from glaip_sdk.cli.utils import (
44
37
  coerce_to_row,
38
+ format_datetime_fields,
45
39
  get_client,
40
+ handle_best_effort_check,
41
+ handle_resource_export,
46
42
  output_list,
47
43
  output_result,
48
44
  spinner_context,
49
45
  )
50
46
  from glaip_sdk.icons import ICON_TOOL
51
- from glaip_sdk.utils import format_datetime
52
47
  from glaip_sdk.utils.import_export import merge_import_with_cli_args
48
+ from rich.console import Console
53
49
 
54
50
  console = Console()
55
51
 
@@ -60,17 +56,32 @@ def tools_group() -> None:
60
56
  pass
61
57
 
62
58
 
63
- def _resolve_tool(
64
- ctx: Any, client: Any, ref: str, select: int | None = None
65
- ) -> Any | None:
66
- """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
67
78
  return resolve_resource_reference(
68
79
  ctx,
69
80
  client,
70
81
  ref,
71
82
  "tool",
72
- client.get_tool,
73
- client.find_tools,
83
+ get_by_id,
84
+ find_by_name,
74
85
  "Tool",
75
86
  select=select,
76
87
  )
@@ -104,22 +115,20 @@ def _validate_name_match(provided: str | None, internal: str) -> str:
104
115
 
105
116
  def _check_duplicate_name(client: Any, tool_name: str) -> None:
106
117
  """Raise if a tool with the same name already exists."""
107
- try:
118
+
119
+ def _check_duplicate() -> None:
108
120
  existing = client.find_tools(name=tool_name)
109
121
  if existing:
110
122
  raise click.ClickException(
111
123
  f"A tool named '{tool_name}' already exists. "
112
124
  "Please change your plugin's 'name' to a unique value, then re-run."
113
125
  )
114
- except click.ClickException:
115
- # Re-raise ClickException (intended error)
116
- raise
117
- except Exception:
118
- # Non-fatal: best-effort duplicate check for other errors
119
- pass
126
+
127
+ handle_best_effort_check(_check_duplicate)
120
128
 
121
129
 
122
130
  def _parse_tags(tags: str | None) -> list[str]:
131
+ """Return a cleaned list of tag strings from a comma-separated input."""
123
132
  return [t.strip() for t in (tags.split(",") if tags else []) if t.strip()]
124
133
 
125
134
 
@@ -181,9 +190,7 @@ def _validate_creation_parameters(
181
190
  ) -> None:
182
191
  """Validate required parameters for tool creation."""
183
192
  if not file and not import_file:
184
- raise click.ClickException(
185
- "A tool file must be provided. Use --file to specify the tool file to upload."
186
- )
193
+ raise click.ClickException("A tool file must be provided. Use --file to specify the tool file to upload.")
187
194
 
188
195
 
189
196
  @tools_group.command(name="list")
@@ -216,6 +223,14 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
216
223
 
217
224
  # Transform function for safe dictionary access
218
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
+ """
219
234
  row = coerce_to_row(tool, ["id", "name", "framework"])
220
235
  # Ensure id is always a string
221
236
  row["id"] = str(row["id"])
@@ -224,7 +239,7 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
224
239
  output_list(ctx, tools, f"{ICON_TOOL} Available Tools", columns, transform_tool)
225
240
 
226
241
  except Exception as e:
227
- raise click.ClickException(str(e))
242
+ raise click.ClickException(str(e)) from e
228
243
 
229
244
 
230
245
  @tools_group.command()
@@ -263,8 +278,9 @@ def create(
263
278
  tags: tuple[str, ...] | None,
264
279
  import_file: str | None,
265
280
  ) -> None:
266
- """Create a new tool.
281
+ r"""Create a new tool.
267
282
 
283
+ \b
268
284
  Examples:
269
285
  aip tools create tool.py # Create from file
270
286
  aip tools create --import tool.json # Create from exported configuration
@@ -315,7 +331,7 @@ def create(
315
331
  handle_json_output(ctx, error=e)
316
332
  if get_ctx_value(ctx, "view") != "json":
317
333
  display_api_error(e, "tool creation")
318
- raise click.ClickException(str(e))
334
+ raise click.ClickException(str(e)) from e
319
335
 
320
336
 
321
337
  @tools_group.command()
@@ -329,8 +345,9 @@ def create(
329
345
  @output_flags()
330
346
  @click.pass_context
331
347
  def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None:
332
- """Get tool details.
348
+ r"""Get tool details.
333
349
 
350
+ \b
334
351
  Examples:
335
352
  aip tools get my-tool
336
353
  aip tools get my-tool --export tool.json # Exports complete configuration as JSON
@@ -344,37 +361,13 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
344
361
 
345
362
  # Handle export option
346
363
  if export:
347
- export_path = Path(export)
348
- # Auto-detect format from file extension
349
- detected_format = detect_export_format(export_path)
350
-
351
- # Always export comprehensive data - re-fetch tool with full details if needed
352
- try:
353
- with spinner_context(
354
- ctx,
355
- "[bold blue]Fetching complete tool details…[/bold blue]",
356
- console_override=console,
357
- ):
358
- tool = client.get_tool_by_id(tool.id)
359
- except Exception as e:
360
- print_markup(
361
- f"[{WARNING_STYLE}]⚠️ Could not fetch full tool details: {e}[/]",
362
- console=console,
363
- )
364
- print_markup(
365
- f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]",
366
- console=console,
367
- )
368
-
369
- with spinner_context(
364
+ handle_resource_export(
370
365
  ctx,
371
- "[bold blue]Exporting tool configuration…[/bold blue]",
366
+ tool,
367
+ Path(export),
368
+ resource_type="tool",
369
+ get_by_id_func=client.get_tool_by_id,
372
370
  console_override=console,
373
- ):
374
- export_resource_to_file(tool, export_path, detected_format)
375
- print_markup(
376
- f"[{SUCCESS_STYLE}]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/]",
377
- console=console,
378
371
  )
379
372
 
380
373
  # Try to fetch raw API data first to preserve ALL fields
@@ -388,15 +381,7 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
388
381
  if raw_tool_data:
389
382
  # Use raw API data - this preserves ALL fields
390
383
  # Format dates for better display (minimal postprocessing)
391
- formatted_data = raw_tool_data.copy()
392
- if "created_at" in formatted_data:
393
- formatted_data["created_at"] = format_datetime(
394
- formatted_data["created_at"]
395
- )
396
- if "updated_at" in formatted_data:
397
- formatted_data["updated_at"] = format_datetime(
398
- formatted_data["updated_at"]
399
- )
384
+ formatted_data = format_datetime_fields(raw_tool_data)
400
385
 
401
386
  # Display using output_result with raw data
402
387
  output_result(
@@ -427,7 +412,7 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
427
412
  )
428
413
 
429
414
  except Exception as e:
430
- raise click.ClickException(str(e))
415
+ raise click.ClickException(str(e)) from e
431
416
 
432
417
 
433
418
  @tools_group.command()
@@ -461,7 +446,7 @@ def update(
461
446
  ):
462
447
  tool = client.get_tool_by_id(tool_id)
463
448
  except Exception as e:
464
- 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
465
450
 
466
451
  # Prepare update data
467
452
  update_data = {}
@@ -474,16 +459,15 @@ def update(
474
459
  # Update code via file upload (custom tools only)
475
460
  if tool.tool_type != "custom":
476
461
  raise click.ClickException(
477
- 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}'."
478
464
  )
479
465
  with spinner_context(
480
466
  ctx,
481
467
  "[bold blue]Uploading new tool code…[/bold blue]",
482
468
  console_override=console,
483
469
  ):
484
- updated_tool = client.tools.update_tool_via_file(
485
- tool.id, file, framework=tool.framework
486
- )
470
+ updated_tool = client.tools.update_tool_via_file(tool.id, file, framework=tool.framework)
487
471
  handle_rich_output(
488
472
  ctx,
489
473
  markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool code updated from {file}"),
@@ -492,7 +476,8 @@ def update(
492
476
  # Update metadata only (native tools only)
493
477
  if tool.tool_type != "native":
494
478
  raise click.ClickException(
495
- 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}'."
496
481
  )
497
482
  with spinner_context(
498
483
  ctx,
@@ -500,13 +485,9 @@ def update(
500
485
  console_override=console,
501
486
  ):
502
487
  updated_tool = tool.update(**update_data)
503
- handle_rich_output(
504
- ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated")
505
- )
488
+ handle_rich_output(ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated"))
506
489
  else:
507
- handle_rich_output(
508
- ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]")
509
- )
490
+ handle_rich_output(ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]"))
510
491
  return
511
492
 
512
493
  handle_json_output(ctx, updated_tool.model_dump())
@@ -516,7 +497,7 @@ def update(
516
497
  handle_json_output(ctx, error=e)
517
498
  if get_ctx_value(ctx, "view") != "json":
518
499
  display_api_error(e, "tool update")
519
- raise click.ClickException(str(e))
500
+ raise click.ClickException(str(e)) from e
520
501
 
521
502
 
522
503
  @tools_group.command()
@@ -538,7 +519,7 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
538
519
  ):
539
520
  tool = client.get_tool_by_id(tool_id)
540
521
  except Exception as e:
541
- 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
542
523
 
543
524
  # Confirm deletion via centralized display helper
544
525
  if not yes and not display_confirmation_prompt("Tool", tool.name):
@@ -564,7 +545,7 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
564
545
  handle_json_output(ctx, error=e)
565
546
  if get_ctx_value(ctx, "view") != "json":
566
547
  display_api_error(e, "tool deletion")
567
- raise click.ClickException(str(e))
548
+ raise click.ClickException(str(e)) from e
568
549
 
569
550
 
570
551
  @tools_group.command("script")
@@ -591,7 +572,5 @@ def script(ctx: Any, tool_id: str) -> None:
591
572
  except Exception as e:
592
573
  handle_json_output(ctx, error=e)
593
574
  if get_ctx_value(ctx, "view") != "json":
594
- print_markup(
595
- f"[{ERROR_STYLE}]Error getting tool script: {e}[/]", console=console
596
- )
597
- 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