glaip-sdk 0.1.0__py3-none-any.whl → 0.1.2__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 (63) hide show
  1. glaip_sdk/_version.py +1 -3
  2. glaip_sdk/branding.py +2 -6
  3. glaip_sdk/cli/agent_config.py +2 -6
  4. glaip_sdk/cli/auth.py +11 -30
  5. glaip_sdk/cli/commands/agents.py +45 -107
  6. glaip_sdk/cli/commands/configure.py +12 -36
  7. glaip_sdk/cli/commands/mcps.py +26 -63
  8. glaip_sdk/cli/commands/models.py +2 -4
  9. glaip_sdk/cli/commands/tools.py +22 -35
  10. glaip_sdk/cli/commands/update.py +3 -8
  11. glaip_sdk/cli/config.py +1 -3
  12. glaip_sdk/cli/display.py +4 -12
  13. glaip_sdk/cli/io.py +8 -14
  14. glaip_sdk/cli/main.py +10 -30
  15. glaip_sdk/cli/mcp_validators.py +5 -15
  16. glaip_sdk/cli/pager.py +3 -9
  17. glaip_sdk/cli/parsers/json_input.py +11 -22
  18. glaip_sdk/cli/resolution.py +3 -9
  19. glaip_sdk/cli/rich_helpers.py +1 -3
  20. glaip_sdk/cli/slash/agent_session.py +5 -10
  21. glaip_sdk/cli/slash/prompt.py +3 -10
  22. glaip_sdk/cli/slash/session.py +46 -95
  23. glaip_sdk/cli/transcript/cache.py +6 -19
  24. glaip_sdk/cli/transcript/capture.py +6 -20
  25. glaip_sdk/cli/transcript/launcher.py +1 -3
  26. glaip_sdk/cli/transcript/viewer.py +11 -40
  27. glaip_sdk/cli/update_notifier.py +165 -21
  28. glaip_sdk/cli/utils.py +33 -84
  29. glaip_sdk/cli/validators.py +11 -12
  30. glaip_sdk/client/_agent_payloads.py +10 -30
  31. glaip_sdk/client/agents.py +33 -63
  32. glaip_sdk/client/base.py +77 -35
  33. glaip_sdk/client/mcps.py +1 -3
  34. glaip_sdk/client/run_rendering.py +6 -14
  35. glaip_sdk/client/tools.py +8 -24
  36. glaip_sdk/client/validators.py +20 -48
  37. glaip_sdk/exceptions.py +1 -3
  38. glaip_sdk/models.py +14 -33
  39. glaip_sdk/payload_schemas/agent.py +1 -3
  40. glaip_sdk/utils/agent_config.py +4 -14
  41. glaip_sdk/utils/client_utils.py +7 -21
  42. glaip_sdk/utils/display.py +2 -6
  43. glaip_sdk/utils/general.py +1 -3
  44. glaip_sdk/utils/import_export.py +3 -9
  45. glaip_sdk/utils/rendering/formatting.py +2 -5
  46. glaip_sdk/utils/rendering/models.py +2 -6
  47. glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
  48. glaip_sdk/utils/rendering/renderer/base.py +63 -189
  49. glaip_sdk/utils/rendering/renderer/debug.py +4 -14
  50. glaip_sdk/utils/rendering/renderer/panels.py +1 -3
  51. glaip_sdk/utils/rendering/renderer/progress.py +3 -11
  52. glaip_sdk/utils/rendering/renderer/stream.py +7 -19
  53. glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
  54. glaip_sdk/utils/rendering/step_tree_state.py +1 -3
  55. glaip_sdk/utils/rendering/steps.py +29 -83
  56. glaip_sdk/utils/resource_refs.py +4 -13
  57. glaip_sdk/utils/serialization.py +14 -46
  58. glaip_sdk/utils/validation.py +4 -4
  59. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/METADATA +1 -1
  60. glaip_sdk-0.1.2.dist-info/RECORD +82 -0
  61. glaip_sdk-0.1.0.dist-info/RECORD +0 -82
  62. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/WHEEL +0 -0
  63. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -76,9 +76,7 @@ def _is_sensitive_data(val: Any) -> bool:
76
76
  return False
77
77
 
78
78
  sensitive_patterns = {"token", "password", "secret", "key", "credential"}
79
- return any(
80
- pattern in str(k).lower() for k in val.keys() for pattern in sensitive_patterns
81
- )
79
+ return any(pattern in str(k).lower() for k in val.keys() for pattern in sensitive_patterns)
82
80
 
83
81
 
84
82
  def _redact_sensitive_dict(val: dict[str, Any]) -> dict[str, Any]:
@@ -143,13 +141,8 @@ def _build_empty_override_warnings(empty_fields: list[str]) -> list[str]:
143
141
  if not empty_fields:
144
142
  return []
145
143
 
146
- warnings = [
147
- "\n[yellow]⚠️ Warning: Empty values provided via CLI will override import values[/yellow]"
148
- ]
149
- warnings.extend(
150
- f"- [yellow]{field}: will be set to empty string[/yellow]"
151
- for field in empty_fields
152
- )
144
+ warnings = ["\n[yellow]⚠️ Warning: Empty values provided via CLI will override import values[/yellow]"]
145
+ warnings.extend(f"- [yellow]{field}: will be set to empty string[/yellow]" for field in empty_fields)
153
146
  return warnings
154
147
 
155
148
 
@@ -250,9 +243,7 @@ def _build_update_data_from_sources(
250
243
  )
251
244
  if auth is not None:
252
245
  parsed_auth = parse_json_input(auth)
253
- update_data["authentication"] = validate_mcp_auth_structure(
254
- parsed_auth, source="--auth"
255
- )
246
+ update_data["authentication"] = validate_mcp_auth_structure(parsed_auth, source="--auth")
256
247
 
257
248
  return update_data
258
249
 
@@ -308,9 +299,7 @@ def mcps_group() -> None:
308
299
  pass
309
300
 
310
301
 
311
- def _resolve_mcp(
312
- ctx: Any, client: Any, ref: str, select: int | None = None
313
- ) -> Any | None:
302
+ def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
314
303
  """Resolve MCP reference (ID or name) with ambiguity handling.
315
304
 
316
305
  Args:
@@ -546,17 +535,13 @@ def list_mcps(ctx: Any) -> None:
546
535
  row["id"] = str(row["id"])
547
536
  # Truncate config field for display
548
537
  if row["config"] != "N/A":
549
- row["config"] = (
550
- str(row["config"])[:50] + "..."
551
- if len(str(row["config"])) > 50
552
- else str(row["config"])
553
- )
538
+ row["config"] = str(row["config"])[:50] + "..." if len(str(row["config"])) > 50 else str(row["config"])
554
539
  return row
555
540
 
556
541
  output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
557
542
 
558
543
  except Exception as e:
559
- raise click.ClickException(str(e))
544
+ raise click.ClickException(str(e)) from e
560
545
 
561
546
 
562
547
  @mcps_group.command()
@@ -621,9 +606,7 @@ def create(
621
606
  try:
622
607
  client = get_client(ctx)
623
608
 
624
- import_payload = (
625
- _load_import_ready_payload(import_file) if import_file is not None else None
626
- )
609
+ import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
627
610
 
628
611
  merged_payload, missing_fields = _merge_import_payload(
629
612
  import_payload,
@@ -636,8 +619,7 @@ def create(
636
619
 
637
620
  if missing_fields:
638
621
  raise click.ClickException(
639
- "Missing required fields after combining import and CLI values: "
640
- + ", ".join(missing_fields)
622
+ "Missing required fields after combining import and CLI values: " + ", ".join(missing_fields)
641
623
  )
642
624
 
643
625
  effective_name = merged_payload["name"]
@@ -726,9 +708,7 @@ def _handle_mcp_export(
726
708
  f"[{WARNING_STYLE}]⚠️ Could not fetch full MCP details: {e}[/]",
727
709
  console=console,
728
710
  )
729
- print_markup(
730
- f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]", console=console
731
- )
711
+ print_markup(f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]", console=console)
732
712
 
733
713
  # Determine if we should prompt for secrets
734
714
  prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
@@ -736,8 +716,7 @@ def _handle_mcp_export(
736
716
  # Warn user if non-interactive mode forces placeholder usage
737
717
  if not no_auth_prompt and not sys.stdin.isatty():
738
718
  print_markup(
739
- f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. "
740
- "Using placeholder values for secrets.[/]",
719
+ f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. Using placeholder values for secrets.[/]",
741
720
  console=console,
742
721
  )
743
722
 
@@ -772,8 +751,7 @@ def _handle_mcp_export(
772
751
  write_resource_export(export_path, export_payload, detected_format)
773
752
 
774
753
  print_markup(
775
- f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: "
776
- f"{export_path} (format: {detected_format})[/]",
754
+ f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/]",
777
755
  console=console,
778
756
  )
779
757
 
@@ -832,8 +810,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
832
810
  @click.option(
833
811
  "--export",
834
812
  type=click.Path(dir_okay=False, writable=True),
835
- help="Export complete MCP configuration to file "
836
- "(format auto-detected from .json/.yaml extension)",
813
+ help="Export complete MCP configuration to file (format auto-detected from .json/.yaml extension)",
837
814
  )
838
815
  @click.option(
839
816
  "--no-auth-prompt",
@@ -880,15 +857,13 @@ def get(
880
857
 
881
858
  # Handle export option
882
859
  if export:
883
- _handle_mcp_export(
884
- ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder
885
- )
860
+ _handle_mcp_export(ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder)
886
861
 
887
862
  # Display MCP details
888
863
  _display_mcp_details(ctx, client, mcp)
889
864
 
890
865
  except Exception as e:
891
- raise click.ClickException(str(e))
866
+ raise click.ClickException(str(e)) from e
892
867
 
893
868
 
894
869
  @mcps_group.command("tools")
@@ -943,7 +918,7 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
943
918
  )
944
919
 
945
920
  except Exception as e:
946
- raise click.ClickException(str(e))
921
+ raise click.ClickException(str(e)) from e
947
922
 
948
923
 
949
924
  @mcps_group.command("connect")
@@ -996,20 +971,17 @@ def connect(ctx: Any, config_file: str) -> None:
996
971
  handle_json_output(ctx, result)
997
972
  else:
998
973
  success_panel = AIPPanel(
999
- f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n"
1000
- f"[bold]Result:[/bold] {result}",
974
+ f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n[bold]Result:[/bold] {result}",
1001
975
  title="🔌 Connection",
1002
976
  border_style=SUCCESS,
1003
977
  )
1004
978
  console.print(success_panel)
1005
979
 
1006
980
  except Exception as e:
1007
- raise click.ClickException(str(e))
981
+ raise click.ClickException(str(e)) from e
1008
982
 
1009
983
 
1010
- def _generate_update_preview(
1011
- mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]
1012
- ) -> str:
984
+ def _generate_update_preview(mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]) -> str:
1013
985
  """Generate formatted preview of changes for user confirmation.
1014
986
 
1015
987
  Args:
@@ -1020,9 +992,7 @@ def _generate_update_preview(
1020
992
  Returns:
1021
993
  Formatted preview string showing old→new values
1022
994
  """
1023
- lines = [
1024
- f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"
1025
- ]
995
+ lines = [f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"]
1026
996
 
1027
997
  empty_overrides = []
1028
998
 
@@ -1048,9 +1018,7 @@ def _generate_update_preview(
1048
1018
  @mcps_group.command()
1049
1019
  @click.argument("mcp_ref")
1050
1020
  @click.option("--name", help="New MCP name")
1051
- @click.option(
1052
- "--transport", type=click.Choice(["http", "sse"]), help="New transport protocol"
1053
- )
1021
+ @click.option("--transport", type=click.Choice(["http", "sse"]), help="New transport protocol")
1054
1022
  @click.option("--description", help="New description")
1055
1023
  @click.option(
1056
1024
  "--config",
@@ -1121,12 +1089,11 @@ def update(
1121
1089
  client = get_client(ctx)
1122
1090
 
1123
1091
  # Validate that at least one update method is provided
1124
- cli_flags_provided = any(
1125
- v is not None for v in [name, transport, description, config, auth]
1126
- )
1092
+ cli_flags_provided = any(v is not None for v in [name, transport, description, config, auth])
1127
1093
  if not import_file and not cli_flags_provided:
1128
1094
  raise click.ClickException(
1129
- "No update fields specified. Use --import or one of: --name, --transport, --description, --config, --auth"
1095
+ "No update fields specified. Use --import or one of: "
1096
+ "--name, --transport, --description, --config, --auth"
1130
1097
  )
1131
1098
 
1132
1099
  # Resolve MCP using helper function
@@ -1140,18 +1107,14 @@ def update(
1140
1107
  return
1141
1108
 
1142
1109
  # Build update data from import and CLI flags
1143
- update_data = _build_update_data_from_sources(
1144
- import_payload, mcp, name, transport, description, config, auth
1145
- )
1110
+ update_data = _build_update_data_from_sources(import_payload, mcp, name, transport, description, config, auth)
1146
1111
 
1147
1112
  if not update_data:
1148
1113
  raise click.ClickException("No update fields specified")
1149
1114
 
1150
1115
  # Show confirmation preview for import-based updates (unless -y flag)
1151
1116
  if import_payload and not y:
1152
- cli_overrides = _collect_cli_overrides(
1153
- name, transport, description, config, auth
1154
- )
1117
+ cli_overrides = _collect_cli_overrides(name, transport, description, config, auth)
1155
1118
  preview = _generate_update_preview(mcp, update_data, cli_overrides)
1156
1119
  print_markup(preview)
1157
1120
 
@@ -57,9 +57,7 @@ def list_models(ctx: Any) -> None:
57
57
  "base_url": model.get("base_url", "Default") or "Default",
58
58
  }
59
59
 
60
- output_list(
61
- ctx, models, "🧠 Available Language Models", columns, transform_model
62
- )
60
+ output_list(ctx, models, "🧠 Available Language Models", columns, transform_model)
63
61
 
64
62
  except Exception as e:
65
- raise click.ClickException(str(e))
63
+ raise click.ClickException(str(e)) from e
@@ -60,9 +60,7 @@ def tools_group() -> None:
60
60
  pass
61
61
 
62
62
 
63
- def _resolve_tool(
64
- ctx: Any, client: Any, ref: str, select: int | None = None
65
- ) -> Any | None:
63
+ def _resolve_tool(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
66
64
  """Resolve tool reference (ID or name) with ambiguity handling."""
67
65
  return resolve_resource_reference(
68
66
  ctx,
@@ -181,9 +179,7 @@ def _validate_creation_parameters(
181
179
  ) -> None:
182
180
  """Validate required parameters for tool creation."""
183
181
  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
- )
182
+ raise click.ClickException("A tool file must be provided. Use --file to specify the tool file to upload.")
187
183
 
188
184
 
189
185
  @tools_group.command(name="list")
@@ -224,7 +220,7 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
224
220
  output_list(ctx, tools, f"{ICON_TOOL} Available Tools", columns, transform_tool)
225
221
 
226
222
  except Exception as e:
227
- raise click.ClickException(str(e))
223
+ raise click.ClickException(str(e)) from e
228
224
 
229
225
 
230
226
  @tools_group.command()
@@ -315,7 +311,7 @@ def create(
315
311
  handle_json_output(ctx, error=e)
316
312
  if get_ctx_value(ctx, "view") != "json":
317
313
  display_api_error(e, "tool creation")
318
- raise click.ClickException(str(e))
314
+ raise click.ClickException(str(e)) from e
319
315
 
320
316
 
321
317
  @tools_group.command()
@@ -373,7 +369,8 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
373
369
  ):
374
370
  export_resource_to_file(tool, export_path, detected_format)
375
371
  print_markup(
376
- f"[{SUCCESS_STYLE}]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/]",
372
+ f"[{SUCCESS_STYLE}]✅ Complete tool configuration exported to: {export_path} "
373
+ f"(format: {detected_format})[/]",
377
374
  console=console,
378
375
  )
379
376
 
@@ -390,13 +387,9 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
390
387
  # Format dates for better display (minimal postprocessing)
391
388
  formatted_data = raw_tool_data.copy()
392
389
  if "created_at" in formatted_data:
393
- formatted_data["created_at"] = format_datetime(
394
- formatted_data["created_at"]
395
- )
390
+ formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
396
391
  if "updated_at" in formatted_data:
397
- formatted_data["updated_at"] = format_datetime(
398
- formatted_data["updated_at"]
399
- )
392
+ formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
400
393
 
401
394
  # Display using output_result with raw data
402
395
  output_result(
@@ -427,7 +420,7 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
427
420
  )
428
421
 
429
422
  except Exception as e:
430
- raise click.ClickException(str(e))
423
+ raise click.ClickException(str(e)) from e
431
424
 
432
425
 
433
426
  @tools_group.command()
@@ -461,7 +454,7 @@ def update(
461
454
  ):
462
455
  tool = client.get_tool_by_id(tool_id)
463
456
  except Exception as e:
464
- raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}")
457
+ raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}") from e
465
458
 
466
459
  # Prepare update data
467
460
  update_data = {}
@@ -474,16 +467,15 @@ def update(
474
467
  # Update code via file upload (custom tools only)
475
468
  if tool.tool_type != "custom":
476
469
  raise click.ClickException(
477
- f"File updates are only supported for custom tools. Tool '{tool.name}' is of type '{tool.tool_type}'."
470
+ "File updates are only supported for custom tools. "
471
+ f"Tool '{tool.name}' is of type '{tool.tool_type}'."
478
472
  )
479
473
  with spinner_context(
480
474
  ctx,
481
475
  "[bold blue]Uploading new tool code…[/bold blue]",
482
476
  console_override=console,
483
477
  ):
484
- updated_tool = client.tools.update_tool_via_file(
485
- tool.id, file, framework=tool.framework
486
- )
478
+ updated_tool = client.tools.update_tool_via_file(tool.id, file, framework=tool.framework)
487
479
  handle_rich_output(
488
480
  ctx,
489
481
  markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool code updated from {file}"),
@@ -492,7 +484,8 @@ def update(
492
484
  # Update metadata only (native tools only)
493
485
  if tool.tool_type != "native":
494
486
  raise click.ClickException(
495
- f"Metadata updates are only supported for native tools. Tool '{tool.name}' is of type '{tool.tool_type}'."
487
+ "Metadata updates are only supported for native tools. "
488
+ f"Tool '{tool.name}' is of type '{tool.tool_type}'."
496
489
  )
497
490
  with spinner_context(
498
491
  ctx,
@@ -500,13 +493,9 @@ def update(
500
493
  console_override=console,
501
494
  ):
502
495
  updated_tool = tool.update(**update_data)
503
- handle_rich_output(
504
- ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated")
505
- )
496
+ handle_rich_output(ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated"))
506
497
  else:
507
- handle_rich_output(
508
- ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]")
509
- )
498
+ handle_rich_output(ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]"))
510
499
  return
511
500
 
512
501
  handle_json_output(ctx, updated_tool.model_dump())
@@ -516,7 +505,7 @@ def update(
516
505
  handle_json_output(ctx, error=e)
517
506
  if get_ctx_value(ctx, "view") != "json":
518
507
  display_api_error(e, "tool update")
519
- raise click.ClickException(str(e))
508
+ raise click.ClickException(str(e)) from e
520
509
 
521
510
 
522
511
  @tools_group.command()
@@ -538,7 +527,7 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
538
527
  ):
539
528
  tool = client.get_tool_by_id(tool_id)
540
529
  except Exception as e:
541
- raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}")
530
+ raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}") from e
542
531
 
543
532
  # Confirm deletion via centralized display helper
544
533
  if not yes and not display_confirmation_prompt("Tool", tool.name):
@@ -564,7 +553,7 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
564
553
  handle_json_output(ctx, error=e)
565
554
  if get_ctx_value(ctx, "view") != "json":
566
555
  display_api_error(e, "tool deletion")
567
- raise click.ClickException(str(e))
556
+ raise click.ClickException(str(e)) from e
568
557
 
569
558
 
570
559
  @tools_group.command("script")
@@ -591,7 +580,5 @@ def script(ctx: Any, tool_id: str) -> None:
591
580
  except Exception as e:
592
581
  handle_json_output(ctx, error=e)
593
582
  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))
583
+ print_markup(f"[{ERROR_STYLE}]Error getting tool script: {e}[/]", console=console)
584
+ raise click.ClickException(str(e)) from e
@@ -44,22 +44,17 @@ def update_command(include_prerelease: bool) -> None:
44
44
  """Upgrade the glaip-sdk package using pip."""
45
45
  console = Console()
46
46
  upgrade_cmd = _build_upgrade_command(include_prerelease)
47
- console.print(
48
- f"[{ACCENT_STYLE}]Upgrading {PACKAGE_NAME} using[/] "
49
- f"[{INFO_STYLE}]{' '.join(upgrade_cmd)}[/]"
50
- )
47
+ console.print(f"[{ACCENT_STYLE}]Upgrading {PACKAGE_NAME} using[/] [{INFO_STYLE}]{' '.join(upgrade_cmd)}[/]")
51
48
 
52
49
  try:
53
50
  subprocess.run(upgrade_cmd, check=True)
54
51
  except FileNotFoundError as exc:
55
52
  raise click.ClickException(
56
- "Unable to locate Python executable to run pip. "
57
- "Please ensure Python is installed and try again."
53
+ "Unable to locate Python executable to run pip. Please ensure Python is installed and try again."
58
54
  ) from exc
59
55
  except subprocess.CalledProcessError as exc:
60
56
  console.print(
61
- f"[{ERROR_STYLE}]Automatic upgrade failed.[/] "
62
- f"Please run `pip install -U {PACKAGE_NAME}` manually."
57
+ f"[{ERROR_STYLE}]Automatic upgrade failed.[/] Please run `pip install -U {PACKAGE_NAME}` manually."
63
58
  )
64
59
  raise click.ClickException("Automatic upgrade failed.") from exc
65
60
 
glaip_sdk/cli/config.py CHANGED
@@ -47,7 +47,5 @@ def save_config(config: dict[str, Any]) -> None:
47
47
  # Set secure file permissions
48
48
  try:
49
49
  os.chmod(CONFIG_FILE, 0o600)
50
- except (
51
- OSError
52
- ): # pragma: no cover - permission errors are expected in some environments
50
+ except OSError: # pragma: no cover - permission errors are expected in some environments
53
51
  pass
glaip_sdk/cli/display.py CHANGED
@@ -41,9 +41,7 @@ def display_creation_success(
41
41
  # Build additional fields display
42
42
  fields_display = ""
43
43
  if additional_fields:
44
- fields_display = "\n" + "\n".join(
45
- f"{key}: {value}" for key, value in additional_fields.items()
46
- )
44
+ fields_display = "\n" + "\n".join(f"{key}: {value}" for key, value in additional_fields.items())
47
45
 
48
46
  return AIPPanel(
49
47
  f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' created successfully![/]\n\n"
@@ -64,9 +62,7 @@ def display_update_success(resource_type: str, resource_name: str) -> Text:
64
62
  Returns:
65
63
  Rich Text object for display
66
64
  """
67
- return markup_text(
68
- f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' updated successfully[/]"
69
- )
65
+ return markup_text(f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' updated successfully[/]")
70
66
 
71
67
 
72
68
  def display_deletion_success(resource_type: str, resource_name: str) -> Text:
@@ -79,9 +75,7 @@ def display_deletion_success(resource_type: str, resource_name: str) -> Text:
79
75
  Returns:
80
76
  Rich Text object for display
81
77
  """
82
- return markup_text(
83
- f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' deleted successfully[/]"
84
- )
78
+ return markup_text(f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' deleted successfully[/]")
85
79
 
86
80
 
87
81
  def display_api_error(error: Exception, operation: str = "operation") -> None:
@@ -293,9 +287,7 @@ def display_confirmation_prompt(resource_type: str, resource_name: str) -> bool:
293
287
  Returns:
294
288
  True if user confirms, False otherwise
295
289
  """
296
- if not click.confirm(
297
- f"Are you sure you want to delete {resource_type.lower()} '{resource_name}'?"
298
- ):
290
+ if not click.confirm(f"Are you sure you want to delete {resource_type.lower()} '{resource_name}'?"):
299
291
  if console.is_terminal:
300
292
  console.print(Text("Deletion cancelled."))
301
293
  return False
glaip_sdk/cli/io.py CHANGED
@@ -30,9 +30,7 @@ def _create_console() -> "Console":
30
30
  return Console()
31
31
 
32
32
 
33
- def load_resource_from_file_with_validation(
34
- file_path: Path, resource_type: str
35
- ) -> dict[str, Any]:
33
+ def load_resource_from_file_with_validation(file_path: Path, resource_type: str) -> dict[str, Any]:
36
34
  """Load resource data from JSON or YAML file with CLI-friendly error handling.
37
35
 
38
36
  Args:
@@ -47,17 +45,15 @@ def load_resource_from_file_with_validation(
47
45
  """
48
46
  try:
49
47
  return load_resource_from_file(file_path)
50
- except FileNotFoundError:
51
- raise click.ClickException(f"File not found: {file_path}")
48
+ except FileNotFoundError as err:
49
+ raise click.ClickException(f"File not found: {file_path}") from err
52
50
  except ValueError as e:
53
- raise click.ClickException(f"Invalid {resource_type.lower()} file format: {e}")
51
+ raise click.ClickException(f"Invalid {resource_type.lower()} file format: {e}") from e
54
52
  except Exception as e:
55
- raise click.ClickException(f"Failed to load {resource_type.lower()} file: {e}")
53
+ raise click.ClickException(f"Failed to load {resource_type.lower()} file: {e}") from e
56
54
 
57
55
 
58
- def export_resource_to_file_with_validation(
59
- resource: Any, file_path: Path, format: str = "json"
60
- ) -> None:
56
+ def export_resource_to_file_with_validation(resource: Any, file_path: Path, format: str = "json") -> None:
61
57
  """Export resource to file with CLI-friendly error handling.
62
58
 
63
59
  Args:
@@ -73,7 +69,7 @@ def export_resource_to_file_with_validation(
73
69
  export_data = collect_attributes_for_export(resource)
74
70
  write_resource_export(file_path, export_data, format)
75
71
  except Exception as e:
76
- raise click.ClickException(f"Failed to export resource: {e}")
72
+ raise click.ClickException(f"Failed to export resource: {e}") from e
77
73
 
78
74
 
79
75
  def fetch_raw_resource_details(client: Any, resource: Any, resource_type: str) -> Any:
@@ -107,9 +103,7 @@ def fetch_raw_resource_details(client: Any, resource: Any, resource_type: str) -
107
103
  # Direct response
108
104
  return raw_response
109
105
  except Exception as e:
110
- console.print(
111
- f"[{WARNING_STYLE}]Failed to fetch raw {resource_type} details: {e}[/]"
112
- )
106
+ console.print(f"[{WARNING_STYLE}]Failed to fetch raw {resource_type} details: {e}[/]")
113
107
  # Fall back to regular method
114
108
  return None
115
109
  return None
glaip_sdk/cli/main.py CHANGED
@@ -215,9 +215,7 @@ def _validate_config_and_show_error(config: dict, console: Console) -> None:
215
215
  border_style=ERROR,
216
216
  )
217
217
  )
218
- console.print(
219
- f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{_SDK_VERSION}) - Configure to connect"
220
- )
218
+ console.print(f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{_SDK_VERSION}) - Configure to connect")
221
219
  sys.exit(1)
222
220
 
223
221
 
@@ -251,16 +249,11 @@ def _collect_cache_summary() -> tuple[str | None, str | None]:
251
249
  else:
252
250
  size_part = ""
253
251
 
254
- cache_line = (
255
- f"[dim]Saved run history[/dim]: {runs_text}{size_part}"
256
- f" · {cache_stats.cache_dir}"
257
- )
252
+ cache_line = f"[dim]Saved run history[/dim]: {runs_text}{size_part} · {cache_stats.cache_dir}"
258
253
  return cache_line, None
259
254
 
260
255
 
261
- def _display_cache_summary(
262
- console: Console, slash_mode: bool, cache_line: str | None, cache_note: str | None
263
- ) -> None:
256
+ def _display_cache_summary(console: Console, slash_mode: bool, cache_line: str | None, cache_note: str | None) -> None:
264
257
  """Render the cache summary details."""
265
258
  if cache_line:
266
259
  console.print(cache_line)
@@ -268,9 +261,7 @@ def _display_cache_summary(
268
261
  console.print(cache_note)
269
262
 
270
263
 
271
- def _create_and_test_client(
272
- config: dict, console: Console, *, compact: bool = False
273
- ) -> Client:
264
+ def _create_and_test_client(config: dict, console: Console, *, compact: bool = False) -> Client:
274
265
  """Create client and test connection by fetching resources."""
275
266
  # Try to create client
276
267
  client = Client(
@@ -308,13 +299,9 @@ def _create_and_test_client(
308
299
 
309
300
  if compact:
310
301
  connection_summary = "GL AIP reachable"
311
- console.print(
312
- f"[dim]• Base URL[/dim]: {client.api_url} ({connection_summary})"
313
- )
302
+ console.print(f"[dim]• Base URL[/dim]: {client.api_url} ({connection_summary})")
314
303
  console.print(f"[dim]• Agent timeout[/dim]: {DEFAULT_AGENT_RUN_TIMEOUT}s")
315
- console.print(
316
- f"[dim]• Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}"
317
- )
304
+ console.print(f"[dim]• Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}")
318
305
  else:
319
306
  console.print( # pragma: no cover - UI display formatting
320
307
  AIPPanel(
@@ -334,9 +321,7 @@ def _create_and_test_client(
334
321
  status_text = "API call failed"
335
322
  console.print(f"[dim]• Base URL[/dim]: {client.api_url} ({status_text})")
336
323
  console.print(f"[{ERROR_STYLE}]• Error[/]: {e}")
337
- console.print(
338
- "[dim]• Tip[/dim]: Check network connectivity or API permissions and try again."
339
- )
324
+ console.print("[dim]• Tip[/dim]: Check network connectivity or API permissions and try again.")
340
325
  console.print("[dim]• Resources[/dim]: unavailable")
341
326
  else:
342
327
  console.print(
@@ -406,16 +391,12 @@ def status(ctx: Any) -> None:
406
391
  @main.command()
407
392
  def version() -> None:
408
393
  """Show version information."""
409
- branding = AIPBranding.create_from_sdk(
410
- sdk_version=_SDK_VERSION, package_name="glaip-sdk"
411
- )
394
+ branding = AIPBranding.create_from_sdk(sdk_version=_SDK_VERSION, package_name="glaip-sdk")
412
395
  branding.display_version_panel()
413
396
 
414
397
 
415
398
  @main.command()
416
- @click.option(
417
- "--check-only", is_flag=True, help="Only check for updates without installing"
418
- )
399
+ @click.option("--check-only", is_flag=True, help="Only check for updates without installing")
419
400
  @click.option(
420
401
  "--force",
421
402
  is_flag=True,
@@ -429,8 +410,7 @@ def update(check_only: bool, force: bool) -> None:
429
410
  if check_only:
430
411
  console.print(
431
412
  AIPPanel(
432
- "[bold blue]🔍 Checking for updates...[/bold blue]\n\n"
433
- "💡 To install updates, run: aip update",
413
+ "[bold blue]🔍 Checking for updates...[/bold blue]\n\n💡 To install updates, run: aip update",
434
414
  title="📋 Update Check",
435
415
  border_style="blue",
436
416
  )