glaip-sdk 0.0.4__py3-none-any.whl → 0.0.5__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 (47) hide show
  1. glaip_sdk/__init__.py +5 -5
  2. glaip_sdk/branding.py +18 -17
  3. glaip_sdk/cli/__init__.py +1 -1
  4. glaip_sdk/cli/agent_config.py +82 -0
  5. glaip_sdk/cli/commands/__init__.py +3 -3
  6. glaip_sdk/cli/commands/agents.py +570 -673
  7. glaip_sdk/cli/commands/configure.py +2 -2
  8. glaip_sdk/cli/commands/mcps.py +148 -143
  9. glaip_sdk/cli/commands/models.py +1 -1
  10. glaip_sdk/cli/commands/tools.py +250 -179
  11. glaip_sdk/cli/display.py +244 -0
  12. glaip_sdk/cli/io.py +106 -0
  13. glaip_sdk/cli/main.py +14 -18
  14. glaip_sdk/cli/resolution.py +59 -0
  15. glaip_sdk/cli/utils.py +305 -264
  16. glaip_sdk/cli/validators.py +235 -0
  17. glaip_sdk/client/__init__.py +3 -224
  18. glaip_sdk/client/agents.py +631 -191
  19. glaip_sdk/client/base.py +66 -4
  20. glaip_sdk/client/main.py +226 -0
  21. glaip_sdk/client/mcps.py +143 -18
  22. glaip_sdk/client/tools.py +146 -11
  23. glaip_sdk/config/constants.py +10 -1
  24. glaip_sdk/models.py +42 -2
  25. glaip_sdk/rich_components.py +29 -0
  26. glaip_sdk/utils/__init__.py +18 -171
  27. glaip_sdk/utils/agent_config.py +181 -0
  28. glaip_sdk/utils/client_utils.py +159 -79
  29. glaip_sdk/utils/display.py +100 -0
  30. glaip_sdk/utils/general.py +94 -0
  31. glaip_sdk/utils/import_export.py +140 -0
  32. glaip_sdk/utils/rendering/formatting.py +6 -1
  33. glaip_sdk/utils/rendering/renderer/__init__.py +67 -8
  34. glaip_sdk/utils/rendering/renderer/base.py +340 -247
  35. glaip_sdk/utils/rendering/renderer/debug.py +3 -2
  36. glaip_sdk/utils/rendering/renderer/panels.py +11 -10
  37. glaip_sdk/utils/rendering/steps.py +1 -1
  38. glaip_sdk/utils/resource_refs.py +192 -0
  39. glaip_sdk/utils/rich_utils.py +29 -0
  40. glaip_sdk/utils/serialization.py +285 -0
  41. glaip_sdk/utils/validation.py +273 -0
  42. {glaip_sdk-0.0.4.dist-info → glaip_sdk-0.0.5.dist-info}/METADATA +6 -5
  43. glaip_sdk-0.0.5.dist-info/RECORD +55 -0
  44. glaip_sdk/cli/commands/init.py +0 -93
  45. glaip_sdk-0.0.4.dist-info/RECORD +0 -41
  46. {glaip_sdk-0.0.4.dist-info → glaip_sdk-0.0.5.dist-info}/WHEEL +0 -0
  47. {glaip_sdk-0.0.4.dist-info → glaip_sdk-0.0.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,244 @@
1
+ """CLI display utilities for success/failure panels and Rich renderers.
2
+
3
+ This module handles all display-related functionality for CLI commands,
4
+ including success messages, error handling, and output formatting.
5
+
6
+ Authors:
7
+ Raymond Christopher (raymond.christopher@gdplabs.id)
8
+ """
9
+
10
+ import json
11
+ from typing import Any
12
+
13
+ import click
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.text import Text
17
+
18
+ from glaip_sdk.rich_components import AIPPanel
19
+
20
+ console = Console()
21
+
22
+
23
+ def display_creation_success(
24
+ resource_type: str, resource_name: str, resource_id: str, **additional_fields
25
+ ) -> Panel:
26
+ """Create standardized success message for resource creation.
27
+
28
+ Args:
29
+ resource_type: Type of resource (e.g., "Agent", "Tool", "MCP")
30
+ resource_name: Name of the created resource
31
+ resource_id: ID of the created resource
32
+ **additional_fields: Additional fields to display
33
+
34
+ Returns:
35
+ Rich Panel object for display
36
+ """
37
+ # Build additional fields display
38
+ fields_display = ""
39
+ if additional_fields:
40
+ fields_display = "\n" + "\n".join(
41
+ f"{key}: {value}" for key, value in additional_fields.items()
42
+ )
43
+
44
+ return AIPPanel(
45
+ f"[green]✅ {resource_type} '{resource_name}' created successfully![/green]\n\n"
46
+ f"ID: {resource_id}{fields_display}",
47
+ title=f"🤖 {resource_type} Created",
48
+ border_style="green",
49
+ padding=(0, 1),
50
+ )
51
+
52
+
53
+ def display_update_success(resource_type: str, resource_name: str) -> Text:
54
+ """Create standardized success message for resource update.
55
+
56
+ Args:
57
+ resource_type: Type of resource (e.g., "Agent", "Tool", "MCP")
58
+ resource_name: Name of the updated resource
59
+
60
+ Returns:
61
+ Rich Text object for display
62
+ """
63
+ return Text(
64
+ f"[green]✅ {resource_type} '{resource_name}' updated successfully[/green]"
65
+ )
66
+
67
+
68
+ def display_deletion_success(resource_type: str, resource_name: str) -> Text:
69
+ """Create standardized success message for resource deletion.
70
+
71
+ Args:
72
+ resource_type: Type of resource (e.g., "Agent", "Tool", "MCP")
73
+ resource_name: Name of the deleted resource
74
+
75
+ Returns:
76
+ Rich Text object for display
77
+ """
78
+ return Text(
79
+ f"[green]✅ {resource_type} '{resource_name}' deleted successfully[/green]"
80
+ )
81
+
82
+
83
+ def display_api_error(error: Exception, operation: str = "operation") -> None:
84
+ """Display standardized API error message.
85
+
86
+ Args:
87
+ error: The exception that occurred
88
+ operation: Description of the operation that failed
89
+ """
90
+ error_type = type(error).__name__
91
+ console.print(Text(f"[red]Error during {operation}: {error}[/red]"))
92
+ console.print(Text(f"[dim]Error type: {error_type}[/dim]"))
93
+
94
+
95
+ def print_api_error(e: Exception) -> None:
96
+ """Print API error with consistent formatting for both JSON and Rich views.
97
+
98
+ Args:
99
+ e: The exception to format and display
100
+
101
+ Notes:
102
+ - Extracts status_code, error_type, and payload from APIError exceptions
103
+ - Provides consistent error reporting across CLI commands
104
+ - Handles both JSON and Rich output formats
105
+ """
106
+ if hasattr(e, "__dict__"): # Check if it's an APIError-like object
107
+ error_info = {
108
+ "error": str(e),
109
+ "status_code": getattr(e, "status_code", None),
110
+ "error_type": getattr(e, "error_type", None),
111
+ "details": getattr(e, "payload", None),
112
+ }
113
+
114
+ # Filter out None values
115
+ error_info = {k: v for k, v in error_info.items() if v is not None}
116
+
117
+ # For JSON view, just return the structured error
118
+ # (CLI commands handle the JSON formatting)
119
+ if hasattr(e, "status_code"):
120
+ console.print(f"[red]API Error: {e}[/red]")
121
+ if hasattr(e, "status_code"):
122
+ console.print(f"[yellow]Status: {e.status_code}[/yellow]")
123
+ if hasattr(e, "payload"):
124
+ console.print(f"[yellow]Details: {e.payload}[/yellow]")
125
+ else:
126
+ console.print(f"[red]Error: {e}[/red]")
127
+ else:
128
+ console.print(f"[red]Error: {e}[/red]")
129
+
130
+
131
+ _MISSING = object()
132
+
133
+
134
+ def build_resource_result_data(resource: Any, fields: list[str]) -> dict[str, Any]:
135
+ """Return a normalized mapping of ``fields`` extracted from ``resource``."""
136
+
137
+ return {
138
+ field: _normalise_field_value(field, _safe_get_attr(resource, field))
139
+ for field in fields
140
+ }
141
+
142
+
143
+ def _safe_get_attr(resource: Any, field: str) -> Any:
144
+ try:
145
+ return getattr(resource, field, _MISSING)
146
+ except Exception:
147
+ return _MISSING
148
+
149
+
150
+ def _normalise_field_value(field: str, value: Any) -> Any:
151
+ if value is _MISSING:
152
+ return "N/A"
153
+ if hasattr(value, "_mock_name"):
154
+ return "N/A"
155
+ if field == "id":
156
+ return str(value)
157
+ return value
158
+
159
+
160
+ def handle_json_output(
161
+ ctx, data: Any = None, error: Exception = None
162
+ ) -> None: # pragma: no cover - formatting covered via integration tests
163
+ """Handle JSON output format for CLI commands.
164
+
165
+ Args:
166
+ ctx: Click context
167
+ data: Data to output (for successful operations)
168
+ error: Error to output (for failed operations)
169
+ """
170
+ ctx_obj = getattr(ctx, "obj", {}) if ctx is not None else {}
171
+ if not isinstance(ctx_obj, dict):
172
+ ctx_obj = {}
173
+
174
+ if ctx_obj.get("view") == "json":
175
+ if error:
176
+ output_data = {"error": str(error)}
177
+ # Add additional error details if available
178
+ if hasattr(error, "status_code"):
179
+ output_data["status_code"] = error.status_code
180
+ if hasattr(error, "error_type"):
181
+ output_data["error_type"] = error.error_type
182
+ if hasattr(error, "payload"):
183
+ output_data["details"] = error.payload
184
+ else:
185
+ output_data = data if data is not None else {"success": True}
186
+
187
+ click.echo(json.dumps(output_data, indent=2, default=str))
188
+
189
+
190
+ def handle_rich_output(ctx, rich_content: Any = None) -> None:
191
+ """Handle Rich output format for CLI commands.
192
+
193
+ Args:
194
+ ctx: Click context
195
+ rich_content: Rich content to display
196
+ """
197
+ ctx_obj = getattr(ctx, "obj", {}) if ctx is not None else {}
198
+ if not isinstance(ctx_obj, dict):
199
+ ctx_obj = {}
200
+
201
+ if ctx_obj.get("view") != "json" and rich_content:
202
+ console.print(rich_content)
203
+
204
+
205
+ def display_confirmation_prompt(resource_type: str, resource_name: str) -> bool:
206
+ """Display standardized confirmation prompt for destructive operations.
207
+
208
+ Args:
209
+ resource_type: Type of resource (e.g., "Agent", "Tool", "MCP")
210
+ resource_name: Name of the resource
211
+
212
+ Returns:
213
+ True if user confirms, False otherwise
214
+ """
215
+ if not click.confirm(
216
+ f"Are you sure you want to delete {resource_type.lower()} '{resource_name}'?"
217
+ ):
218
+ if console.is_terminal:
219
+ console.print(Text("Deletion cancelled."))
220
+ return False
221
+ return True
222
+
223
+
224
+ def display_agent_run_suggestions(agent: Any) -> Panel:
225
+ """Return a panel with post-creation suggestions for an agent."""
226
+
227
+ return AIPPanel(
228
+ f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
229
+ f"🚀 Run this agent:\n"
230
+ f' [green]aip agents run {agent.id} "Your message here"[/green]\n\n'
231
+ f"📋 Or use the agent name:\n"
232
+ f' [green]aip agents run "{agent.name}" "Your message here"[/green]\n\n'
233
+ f"🔧 Available options:\n"
234
+ f" [dim]--chat-history[/dim] Include previous conversation\n"
235
+ f" [dim]--file[/dim] Attach files\n"
236
+ f" [dim]--input[/dim] Alternative input method\n"
237
+ f" [dim]--timeout[/dim] Set execution timeout\n"
238
+ f" [dim]--save[/dim] Save transcript to file\n"
239
+ f" [dim]--verbose[/dim] Show detailed execution\n\n"
240
+ f"💡 [dim]Input text can be positional OR use --input flag (both work!)[/dim]",
241
+ title="🤖 Ready to Run Agent",
242
+ border_style="blue",
243
+ padding=(0, 1),
244
+ )
glaip_sdk/cli/io.py ADDED
@@ -0,0 +1,106 @@
1
+ """CLI I/O utilities for file import/export orchestration.
2
+
3
+ This module handles file operations and network requests for CLI commands,
4
+ wrapping core serialization utilities with Click-friendly error handling.
5
+
6
+ Authors:
7
+ Raymond Christopher (raymond.christopher@gdplabs.id)
8
+ """
9
+
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ import click
14
+
15
+ from glaip_sdk.utils.serialization import (
16
+ collect_attributes_for_export,
17
+ load_resource_from_file,
18
+ write_resource_export,
19
+ )
20
+
21
+
22
+ def load_resource_from_file_with_validation(
23
+ file_path: Path, resource_type: str
24
+ ) -> dict[str, Any]:
25
+ """Load resource data from JSON or YAML file with CLI-friendly error handling.
26
+
27
+ Args:
28
+ file_path: Path to the file
29
+ resource_type: Type of resource (for error messages)
30
+
31
+ Returns:
32
+ Dictionary with resource data
33
+
34
+ Raises:
35
+ click.ClickException: If file operations fail
36
+ """
37
+ try:
38
+ return load_resource_from_file(file_path)
39
+ except FileNotFoundError:
40
+ raise click.ClickException(f"File not found: {file_path}")
41
+ except ValueError as e:
42
+ raise click.ClickException(f"Invalid {resource_type.lower()} file format: {e}")
43
+ except Exception as e:
44
+ raise click.ClickException(f"Failed to load {resource_type.lower()} file: {e}")
45
+
46
+
47
+ def export_resource_to_file_with_validation(
48
+ resource: Any, file_path: Path, format: str = "json"
49
+ ) -> None:
50
+ """Export resource to file with CLI-friendly error handling.
51
+
52
+ Args:
53
+ resource: Resource object to export
54
+ file_path: Path to export file
55
+ format: Export format ("json" or "yaml")
56
+
57
+ Raises:
58
+ click.ClickException: If export operations fail
59
+ """
60
+ try:
61
+ # Get all available resource attributes dynamically
62
+ export_data = collect_attributes_for_export(resource)
63
+ write_resource_export(file_path, export_data, format)
64
+ except Exception as e:
65
+ raise click.ClickException(f"Failed to export resource: {e}")
66
+
67
+
68
+ def fetch_raw_resource_details(client, resource, resource_type: str):
69
+ """Fetch raw resource details directly from API to preserve ALL fields.
70
+
71
+ Args:
72
+ client: API client
73
+ resource: Resource object
74
+ resource_type: Type of resource ("agents", "tools", "mcps")
75
+
76
+ Returns:
77
+ Raw API response data or None if failed
78
+
79
+ Notes:
80
+ This is CLI-specific functionality for displaying comprehensive resource details.
81
+ """
82
+ from rich.console import Console
83
+
84
+ console = Console()
85
+
86
+ try:
87
+ resource_id = str(getattr(resource, "id", "")).strip()
88
+ if resource_id:
89
+ # Make direct API call to get raw response
90
+ response = client.http_client.get(f"/{resource_type}/{resource_id}")
91
+ response.raise_for_status()
92
+ raw_response = response.json()
93
+
94
+ # If it's a wrapped response (success/data/message), extract the data
95
+ if isinstance(raw_response, dict) and "data" in raw_response:
96
+ return raw_response["data"]
97
+ else:
98
+ # Direct response
99
+ return raw_response
100
+ except Exception as e:
101
+ console.print(
102
+ f"[yellow]Failed to fetch raw {resource_type} details: {e}[/yellow]"
103
+ )
104
+ # Fall back to regular method
105
+ return None
106
+ return None
glaip_sdk/cli/main.py CHANGED
@@ -10,8 +10,6 @@ import sys
10
10
 
11
11
  import click
12
12
  from rich.console import Console
13
- from rich.panel import Panel
14
- from rich.table import Table
15
13
 
16
14
  from glaip_sdk import Client
17
15
  from glaip_sdk._version import __version__ as _SDK_VERSION
@@ -22,11 +20,11 @@ from glaip_sdk.cli.commands.configure import (
22
20
  configure_command,
23
21
  load_config,
24
22
  )
25
- from glaip_sdk.cli.commands.init import init_command
26
23
  from glaip_sdk.cli.commands.mcps import mcps_group
27
24
  from glaip_sdk.cli.commands.models import models_group
28
25
  from glaip_sdk.cli.commands.tools import tools_group
29
26
  from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
27
+ from glaip_sdk.rich_components import AIPPanel, AIPTable
30
28
 
31
29
 
32
30
  @click.group()
@@ -44,15 +42,14 @@ from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
44
42
  @click.option("--no-tty", is_flag=True, help="Disable TTY renderer")
45
43
  @click.pass_context
46
44
  def main(ctx, api_url, api_key, timeout, view, no_tty):
47
- """AIP SDK Command Line Interface.
45
+ """GL AIP SDK Command Line Interface.
48
46
 
49
- A comprehensive CLI for managing AI Agent Platform resources including
47
+ A comprehensive CLI for managing GL AIP resources including
50
48
  agents, tools, MCPs, and more.
51
49
 
52
50
  Examples:
53
51
  aip version # Show detailed version info
54
52
  aip configure # Configure credentials
55
- aip init # Initialize configuration
56
53
  aip agents list # List all agents
57
54
  aip tools create my_tool.py # Create a new tool
58
55
  aip agents run my-agent "Hello world" # Run an agent
@@ -74,7 +71,6 @@ main.add_command(config_group)
74
71
  main.add_command(tools_group)
75
72
  main.add_command(mcps_group)
76
73
  main.add_command(models_group)
77
- main.add_command(init_command)
78
74
 
79
75
  # Add top-level commands
80
76
  main.add_command(configure_command)
@@ -117,7 +113,7 @@ def status(ctx):
117
113
 
118
114
  if not config.get("api_url") or not config.get("api_key"):
119
115
  console.print(
120
- Panel(
116
+ AIPPanel(
121
117
  "[bold red]❌ Configuration incomplete[/bold red]\n\n"
122
118
  f"🔍 Current config:\n"
123
119
  f" • API URL: {config.get('api_url', 'Not set')}\n"
@@ -145,7 +141,7 @@ def status(ctx):
145
141
  mcps = client.list_mcps()
146
142
 
147
143
  # Create status table
148
- table = Table(title="🔗 AIP Platform Status")
144
+ table = AIPTable(title="🔗 GL AIP Status")
149
145
  table.add_column("Resource", style="cyan", width=15)
150
146
  table.add_column("Count", style="green", width=10)
151
147
  table.add_column("Status", style="green", width=15)
@@ -155,8 +151,8 @@ def status(ctx):
155
151
  table.add_row("MCPs", str(len(mcps)), "✅ Available")
156
152
 
157
153
  console.print(
158
- Panel(
159
- f"[bold green]✅ Connected to AIP Platform[/bold green]\n"
154
+ AIPPanel(
155
+ f"[bold green]✅ Connected to GL AIP[/bold green]\n"
160
156
  f"🔗 API URL: {client.api_url}\n"
161
157
  f"🤖 Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
162
158
  title="🚀 Connection Status",
@@ -168,7 +164,7 @@ def status(ctx):
168
164
 
169
165
  except Exception as e:
170
166
  console.print(
171
- Panel(
167
+ AIPPanel(
172
168
  f"[bold yellow]⚠️ Connection established but API call failed[/bold yellow]\n"
173
169
  f"🔗 API URL: {client.api_url}\n"
174
170
  f"❌ Error: {e}\n\n"
@@ -185,7 +181,7 @@ def status(ctx):
185
181
 
186
182
  except Exception as e:
187
183
  console.print(
188
- Panel(
184
+ AIPPanel(
189
185
  f"[bold red]❌ Connection failed[/bold red]\n\n"
190
186
  f"🔍 Error: {e}\n\n"
191
187
  f"💡 Troubleshooting steps:\n"
@@ -225,7 +221,7 @@ def update(check_only: bool, force: bool):
225
221
 
226
222
  if check_only:
227
223
  console.print(
228
- Panel(
224
+ AIPPanel(
229
225
  "[bold blue]🔍 Checking for updates...[/bold blue]\n\n"
230
226
  "💡 To install updates, run: aip update",
231
227
  title="📋 Update Check",
@@ -235,7 +231,7 @@ def update(check_only: bool, force: bool):
235
231
  return
236
232
 
237
233
  console.print(
238
- Panel(
234
+ AIPPanel(
239
235
  "[bold blue]🔄 Updating AIP SDK...[/bold blue]\n\n"
240
236
  "📦 This will update the package from PyPI\n"
241
237
  "💡 Use --check-only to just check for updates",
@@ -260,7 +256,7 @@ def update(check_only: bool, force: bool):
260
256
  subprocess.run(cmd, capture_output=True, text=True, check=True)
261
257
 
262
258
  console.print(
263
- Panel(
259
+ AIPPanel(
264
260
  "[bold green]✅ Update successful![/bold green]\n\n"
265
261
  "🔄 AIP SDK has been updated to the latest version\n"
266
262
  "💡 Restart your terminal or run 'aip --version' to verify",
@@ -281,7 +277,7 @@ def update(check_only: bool, force: bool):
281
277
 
282
278
  except subprocess.CalledProcessError as e:
283
279
  console.print(
284
- Panel(
280
+ AIPPanel(
285
281
  f"[bold red]❌ Update failed[/bold red]\n\n"
286
282
  f"🔍 Error: {e.stderr}\n\n"
287
283
  "💡 Troubleshooting:\n"
@@ -297,7 +293,7 @@ def update(check_only: bool, force: bool):
297
293
 
298
294
  except ImportError:
299
295
  console.print(
300
- Panel(
296
+ AIPPanel(
301
297
  "[bold red]❌ Rich library not available[/bold red]\n\n"
302
298
  "💡 Install rich: pip install rich\n"
303
299
  " Then try: aip update",
@@ -0,0 +1,59 @@
1
+ """CLI resource resolution utilities for handling ID/name references.
2
+
3
+ This module provides CLI-specific resource resolution functionality,
4
+ including interactive pickers and ambiguity handling.
5
+
6
+ Authors:
7
+ Raymond Christopher (raymond.christopher@gdplabs.id)
8
+ """
9
+
10
+ from collections.abc import Callable
11
+
12
+ import click
13
+
14
+ from glaip_sdk.cli.utils import resolve_resource
15
+
16
+
17
+ def resolve_resource_reference(
18
+ ctx,
19
+ _client,
20
+ reference: str,
21
+ resource_type: str,
22
+ get_by_id_func: Callable,
23
+ find_by_name_func: Callable,
24
+ label: str,
25
+ select: int = None,
26
+ interface_preference: str | None = None,
27
+ ):
28
+ """Resolve resource reference (ID or name) with ambiguity handling.
29
+
30
+ This is a common pattern used across all resource types.
31
+
32
+ Args:
33
+ ctx: Click context
34
+ client: API client
35
+ reference: Resource ID or name
36
+ resource_type: Type of resource
37
+ get_by_id_func: Function to get resource by ID
38
+ find_by_name_func: Function to find resources by name
39
+ label: Label for error messages
40
+ select: Selection index for ambiguous matches
41
+
42
+ Returns:
43
+ Resolved resource object
44
+
45
+ Raises:
46
+ click.ClickException: If resolution fails
47
+ """
48
+ try:
49
+ return resolve_resource(
50
+ ctx,
51
+ reference,
52
+ get_by_id=get_by_id_func,
53
+ find_by_name=find_by_name_func,
54
+ label=label,
55
+ select=select,
56
+ interface_preference=interface_preference,
57
+ )
58
+ except Exception as e:
59
+ raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}")