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
@@ -11,12 +11,12 @@ from pathlib import Path
11
11
  import click
12
12
  import yaml
13
13
  from rich.console import Console
14
- from rich.table import Table
15
14
  from rich.text import Text
16
15
 
17
16
  from glaip_sdk import Client
18
17
  from glaip_sdk._version import __version__ as _SDK_VERSION
19
18
  from glaip_sdk.branding import AIPBranding
19
+ from glaip_sdk.rich_components import AIPTable
20
20
 
21
21
  console = Console()
22
22
 
@@ -68,7 +68,7 @@ def list_config():
68
68
  )
69
69
  return
70
70
 
71
- table = Table(title="🔧 AIP Configuration")
71
+ table = AIPTable(title="🔧 AIP Configuration")
72
72
  table.add_column("Setting", style="cyan", width=20)
73
73
  table.add_column("Value", style="green")
74
74
 
@@ -5,21 +5,37 @@ Authors:
5
5
  """
6
6
 
7
7
  import json
8
+ from pathlib import Path
8
9
 
9
10
  import click
10
11
  from rich.console import Console
11
- from rich.panel import Panel
12
- from rich.table import Table
13
12
  from rich.text import Text
14
13
 
15
- from ..utils import (
14
+ from glaip_sdk.cli.display import (
15
+ display_api_error,
16
+ display_confirmation_prompt,
17
+ display_creation_success,
18
+ display_deletion_success,
19
+ display_update_success,
20
+ handle_json_output,
21
+ handle_rich_output,
22
+ )
23
+ from glaip_sdk.cli.io import (
24
+ export_resource_to_file_with_validation as export_resource_to_file,
25
+ )
26
+ from glaip_sdk.cli.io import (
27
+ fetch_raw_resource_details,
28
+ )
29
+ from glaip_sdk.cli.resolution import resolve_resource_reference
30
+ from glaip_sdk.cli.utils import (
16
31
  coerce_to_row,
17
32
  get_client,
18
33
  output_flags,
19
34
  output_list,
20
35
  output_result,
21
- resolve_resource,
22
36
  )
37
+ from glaip_sdk.rich_components import AIPPanel
38
+ from glaip_sdk.utils import format_datetime
23
39
 
24
40
  console = Console()
25
41
 
@@ -32,12 +48,14 @@ def mcps_group():
32
48
 
33
49
  def _resolve_mcp(ctx, client, ref, select=None):
34
50
  """Resolve MCP reference (ID or name) with ambiguity handling."""
35
- return resolve_resource(
51
+ return resolve_resource_reference(
36
52
  ctx,
53
+ client,
37
54
  ref,
38
- get_by_id=client.mcps.get_mcp_by_id,
39
- find_by_name=client.mcps.find_mcps,
40
- label="MCP",
55
+ "mcp",
56
+ client.mcps.get_mcp_by_id,
57
+ client.mcps.find_mcps,
58
+ "MCP",
41
59
  select=select,
42
60
  )
43
61
 
@@ -106,55 +124,117 @@ def create(ctx, name, transport, description, config):
106
124
  config=mcp_config,
107
125
  )
108
126
 
109
- view = (ctx.obj or {}).get("view", "rich")
110
- if view == "json":
111
- click.echo(json.dumps(mcp.model_dump(), indent=2))
112
- else:
113
- # Rich output
114
- panel = Panel(
115
- f"[green]✅ MCP '{mcp.name}' created successfully![/green]\n\n"
116
- f"ID: {mcp.id}\n"
117
- f"Type: server (default)\n"
118
- f"Description: {description or 'No description'}",
119
- title="🔌 MCP Created",
120
- border_style="green",
121
- )
122
- console.print(panel)
127
+ # Handle JSON output
128
+ handle_json_output(ctx, mcp.model_dump())
129
+
130
+ # Handle Rich output
131
+ rich_panel = display_creation_success(
132
+ "MCP",
133
+ mcp.name,
134
+ mcp.id,
135
+ Type="server",
136
+ Transport=getattr(mcp, "transport", transport),
137
+ Description=description or "No description",
138
+ )
139
+ handle_rich_output(ctx, rich_panel)
123
140
 
124
141
  except Exception as e:
125
- view = (ctx.obj or {}).get("view", "rich")
126
- if view == "json":
127
- click.echo(json.dumps({"error": str(e)}, indent=2))
128
- else:
129
- console.print(Text(f"[red]Error creating MCP: {e}[/red]"))
142
+ handle_json_output(ctx, error=e)
143
+ if ctx.obj.get("view") != "json":
144
+ display_api_error(e, "MCP creation")
130
145
  raise click.ClickException(str(e))
131
146
 
132
147
 
133
148
  @mcps_group.command()
134
149
  @click.argument("mcp_ref")
150
+ @click.option(
151
+ "--export",
152
+ type=click.Path(dir_okay=False, writable=True),
153
+ help="Export complete MCP configuration to file (format auto-detected from .json/.yaml extension)",
154
+ )
135
155
  @output_flags()
136
156
  @click.pass_context
137
- def get(ctx, mcp_ref):
138
- """Get MCP details."""
157
+ def get(ctx, mcp_ref, export):
158
+ """Get MCP details.
159
+
160
+ Examples:
161
+ aip mcps get my-mcp
162
+ aip mcps get my-mcp --export mcp.json # Exports complete configuration as JSON
163
+ aip mcps get my-mcp --export mcp.yaml # Exports complete configuration as YAML
164
+ """
139
165
  try:
140
166
  client = get_client(ctx)
141
167
 
142
168
  # Resolve MCP using helper function
143
169
  mcp = _resolve_mcp(ctx, client, mcp_ref)
144
170
 
145
- # Create result data with actual available fields
146
- result_data = {
147
- "id": str(getattr(mcp, "id", "N/A")),
148
- "name": getattr(mcp, "name", "N/A"),
149
- "type": getattr(mcp, "type", "N/A"),
150
- "config": getattr(mcp, "config", "N/A"),
151
- "status": getattr(mcp, "status", "N/A"),
152
- "connection_status": getattr(mcp, "connection_status", "N/A"),
153
- }
154
-
155
- output_result(
156
- ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
157
- )
171
+ # Handle export option
172
+ if export:
173
+ export_path = Path(export)
174
+ # Auto-detect format from file extension
175
+ if export_path.suffix.lower() in [".yaml", ".yml"]:
176
+ detected_format = "yaml"
177
+ else:
178
+ detected_format = "json"
179
+
180
+ # Always export comprehensive data - re-fetch MCP with full details if needed
181
+ try:
182
+ mcp = client.mcps.get_mcp_by_id(mcp.id)
183
+ except Exception as e:
184
+ console.print(
185
+ Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
186
+ )
187
+ console.print(
188
+ Text("[yellow]⚠️ Proceeding with available data[/yellow]")
189
+ )
190
+
191
+ export_resource_to_file(mcp, export_path, detected_format)
192
+ console.print(
193
+ Text(
194
+ f"[green]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/green]"
195
+ )
196
+ )
197
+
198
+ # Try to fetch raw API data first to preserve ALL fields
199
+ raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
200
+
201
+ if raw_mcp_data:
202
+ # Use raw API data - this preserves ALL fields
203
+ # Format dates for better display (minimal postprocessing)
204
+ formatted_data = raw_mcp_data.copy()
205
+ if "created_at" in formatted_data:
206
+ formatted_data["created_at"] = format_datetime(
207
+ formatted_data["created_at"]
208
+ )
209
+ if "updated_at" in formatted_data:
210
+ formatted_data["updated_at"] = format_datetime(
211
+ formatted_data["updated_at"]
212
+ )
213
+
214
+ # Display using output_result with raw data
215
+ output_result(
216
+ ctx,
217
+ formatted_data,
218
+ title="MCP Details",
219
+ panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
220
+ )
221
+ else:
222
+ # Fall back to original method if raw fetch fails
223
+ console.print("[yellow]Falling back to Pydantic model data[/yellow]")
224
+
225
+ # Create result data with actual available fields
226
+ result_data = {
227
+ "id": str(getattr(mcp, "id", "N/A")),
228
+ "name": getattr(mcp, "name", "N/A"),
229
+ "type": getattr(mcp, "type", "N/A"),
230
+ "config": getattr(mcp, "config", "N/A"),
231
+ "status": getattr(mcp, "status", "N/A"),
232
+ "connection_status": getattr(mcp, "connection_status", "N/A"),
233
+ }
234
+
235
+ output_result(
236
+ ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
237
+ )
158
238
 
159
239
  except Exception as e:
160
240
  raise click.ClickException(str(e))
@@ -200,65 +280,7 @@ def list_tools(ctx, mcp_ref):
200
280
  raise click.ClickException(str(e))
201
281
 
202
282
 
203
- @mcps_group.command("tools-from-config")
204
- @click.option(
205
- "--from-file",
206
- "config_file",
207
- type=click.Path(exists=True),
208
- required=True,
209
- help="MCP config JSON file",
210
- )
211
- @output_flags()
212
- @click.pass_context
213
- def tools_from_config(ctx, config_file):
214
- """Fetch tools from MCP config."""
215
- try:
216
- client = get_client(ctx)
217
-
218
- # Load MCP config from file
219
- with open(config_file) as f:
220
- config = json.load(f)
221
-
222
- view = (ctx.obj or {}).get("view", "rich")
223
- if view != "json":
224
- console.print(
225
- f"[yellow]Fetching tools from MCP config in {config_file}...[/yellow]"
226
- )
227
-
228
- # Get tools from MCP config
229
- tools = client.mcps.get_mcp_tools_from_config(config)
230
-
231
- view = (ctx.obj or {}).get("view", "rich")
232
- if view == "json":
233
- click.echo(json.dumps(tools, indent=2))
234
- else: # rich output
235
- if tools:
236
- table = Table(
237
- title="🔧 Tools from MCP Config",
238
- show_header=True,
239
- header_style="bold magenta",
240
- )
241
- table.add_column("Name", style="cyan", no_wrap=True)
242
- table.add_column("Description", style="green")
243
- table.add_column("Type", style="yellow")
244
-
245
- for tool in tools:
246
- table.add_row(
247
- tool.get("name", "N/A"),
248
- tool.get("description", "N/A")[:50] + "..."
249
- if len(tool.get("description", "")) > 50
250
- else tool.get("description", "N/A"),
251
- tool.get("type", "N/A"),
252
- )
253
- console.print(table)
254
- else:
255
- console.print(Text("[yellow]No tools found in MCP config[/yellow]"))
256
-
257
- except Exception as e:
258
- raise click.ClickException(str(e))
259
-
260
-
261
- @mcps_group.command("test-connection")
283
+ @mcps_group.command("connect")
262
284
  @click.option(
263
285
  "--from-file",
264
286
  "config_file",
@@ -267,8 +289,8 @@ def tools_from_config(ctx, config_file):
267
289
  )
268
290
  @output_flags()
269
291
  @click.pass_context
270
- def test_connection(ctx, config_file):
271
- """Test MCP connection using config file."""
292
+ def connect(ctx, config_file):
293
+ """Connect to MCP using config file."""
272
294
  try:
273
295
  client = get_client(ctx)
274
296
 
@@ -280,7 +302,7 @@ def test_connection(ctx, config_file):
280
302
  if view != "json":
281
303
  console.print(
282
304
  Text(
283
- f"[yellow]Testing MCP connection with config from {config_file}...[/yellow]"
305
+ f"[yellow]Connecting to MCP with config from {config_file}...[/yellow]"
284
306
  )
285
307
  )
286
308
 
@@ -289,12 +311,12 @@ def test_connection(ctx, config_file):
289
311
 
290
312
  view = (ctx.obj or {}).get("view", "rich")
291
313
  if view == "json":
292
- click.echo(json.dumps(result, indent=2))
314
+ handle_json_output(ctx, result)
293
315
  else:
294
- success_panel = Panel(
295
- f"[green]✓[/green] MCP connection test successful!\n\n"
316
+ success_panel = AIPPanel(
317
+ f"[green]✓[/green] MCP connection successful!\n\n"
296
318
  f"[bold]Result:[/bold] {result}",
297
- title="🔌 Connection Test",
319
+ title="🔌 Connection",
298
320
  border_style="green",
299
321
  )
300
322
  console.print(success_panel)
@@ -333,23 +355,16 @@ def update(ctx, mcp_ref, name, description, config):
333
355
  if not update_data:
334
356
  raise click.ClickException("No update fields specified")
335
357
 
336
- # Update MCP
358
+ # Update MCP (automatically chooses PUT or PATCH based on provided fields)
337
359
  updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
338
360
 
339
- view = (ctx.obj or {}).get("view", "rich")
340
- if view == "json":
341
- click.echo(json.dumps(updated_mcp.model_dump(), indent=2))
342
- else:
343
- console.print(
344
- Text(f"[green]✅ MCP '{updated_mcp.name}' updated successfully[/green]")
345
- )
361
+ handle_json_output(ctx, updated_mcp.model_dump())
362
+ handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
346
363
 
347
364
  except Exception as e:
348
- view = (ctx.obj or {}).get("view", "rich")
349
- if view == "json":
350
- click.echo(json.dumps({"error": str(e)}, indent=2))
351
- else:
352
- console.print(Text(f"[red]Error updating MCP: {e}[/red]"))
365
+ handle_json_output(ctx, error=e)
366
+ if (ctx.obj or {}).get("view") != "json":
367
+ display_api_error(e, "MCP update")
353
368
  raise click.ClickException(str(e))
354
369
 
355
370
 
@@ -367,32 +382,22 @@ def delete(ctx, mcp_ref, yes):
367
382
  mcp = _resolve_mcp(ctx, client, mcp_ref)
368
383
 
369
384
  # Confirm deletion
370
- if not yes and not click.confirm(
371
- f"Are you sure you want to delete MCP '{mcp.name}'?"
372
- ):
373
- view = (ctx.obj or {}).get("view", "rich")
374
- if view != "json":
375
- console.print(Text("Deletion cancelled."))
385
+ if not yes and not display_confirmation_prompt("MCP", mcp.name):
376
386
  return
377
387
 
378
388
  client.mcps.delete_mcp(mcp.id)
379
389
 
380
- view = (ctx.obj or {}).get("view", "rich")
381
- if view == "json":
382
- click.echo(
383
- json.dumps(
384
- {"success": True, "message": f"MCP '{mcp.name}' deleted"}, indent=2
385
- )
386
- )
387
- else:
388
- console.print(
389
- Text(f"[green]✅ MCP '{mcp.name}' deleted successfully[/green]")
390
- )
390
+ handle_json_output(
391
+ ctx,
392
+ {
393
+ "success": True,
394
+ "message": f"MCP '{mcp.name}' deleted",
395
+ },
396
+ )
397
+ handle_rich_output(ctx, display_deletion_success("MCP", mcp.name))
391
398
 
392
399
  except Exception as e:
393
- view = (ctx.obj or {}).get("view", "rich")
394
- if view == "json":
395
- click.echo(json.dumps({"error": str(e)}, indent=2))
396
- else:
397
- console.print(Text(f"[red]Error deleting MCP: {e}[/red]"))
400
+ handle_json_output(ctx, error=e)
401
+ if (ctx.obj or {}).get("view") != "json":
402
+ display_api_error(e, "MCP deletion")
398
403
  raise click.ClickException(str(e))
@@ -7,7 +7,7 @@ Authors:
7
7
  import click
8
8
  from rich.console import Console
9
9
 
10
- from ..utils import get_client, output_flags, output_list
10
+ from glaip_sdk.cli.utils import get_client, output_flags, output_list
11
11
 
12
12
  console = Console()
13
13