glaip-sdk 0.0.3__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 +146 -0
  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 +786 -271
  7. glaip_sdk/cli/commands/configure.py +19 -19
  8. glaip_sdk/cli/commands/mcps.py +151 -141
  9. glaip_sdk/cli/commands/models.py +1 -1
  10. glaip_sdk/cli/commands/tools.py +252 -178
  11. glaip_sdk/cli/display.py +244 -0
  12. glaip_sdk/cli/io.py +106 -0
  13. glaip_sdk/cli/main.py +27 -20
  14. glaip_sdk/cli/resolution.py +59 -0
  15. glaip_sdk/cli/utils.py +372 -213
  16. glaip_sdk/cli/validators.py +235 -0
  17. glaip_sdk/client/__init__.py +3 -224
  18. glaip_sdk/client/agents.py +632 -171
  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 +327 -104
  23. glaip_sdk/config/constants.py +10 -1
  24. glaip_sdk/models.py +43 -3
  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.3.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 -177
  45. glaip_sdk-0.0.3.dist-info/RECORD +0 -40
  46. {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/WHEEL +0 -0
  47. {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/entry_points.txt +0 -0
@@ -11,10 +11,12 @@ from pathlib import Path
11
11
  import click
12
12
  import yaml
13
13
  from rich.console import Console
14
- from rich.panel import Panel
15
- from rich.table import Table
14
+ from rich.text import Text
16
15
 
17
16
  from glaip_sdk import Client
17
+ from glaip_sdk._version import __version__ as _SDK_VERSION
18
+ from glaip_sdk.branding import AIPBranding
19
+ from glaip_sdk.rich_components import AIPTable
18
20
 
19
21
  console = Console()
20
22
 
@@ -66,7 +68,7 @@ def list_config():
66
68
  )
67
69
  return
68
70
 
69
- table = Table(title="🔧 AIP Configuration")
71
+ table = AIPTable(title="🔧 AIP Configuration")
70
72
  table.add_column("Setting", style="cyan", width=20)
71
73
  table.add_column("Value", style="green")
72
74
 
@@ -79,7 +81,7 @@ def list_config():
79
81
  table.add_row(key, str(value))
80
82
 
81
83
  console.print(table)
82
- console.print(f"\n📁 Config file: {CONFIG_FILE}")
84
+ console.print(Text(f"\n📁 Config file: {CONFIG_FILE}"))
83
85
 
84
86
 
85
87
  @config_group.command("set")
@@ -102,9 +104,9 @@ def set_config(key, value):
102
104
 
103
105
  if key == "api_key":
104
106
  masked_value = "***" + value[-4:] if len(value) > 4 else "***"
105
- console.print(f"✅ Set {key} = {masked_value}")
107
+ console.print(Text(f"✅ Set {key} = {masked_value}"))
106
108
  else:
107
- console.print(f"✅ Set {key} = {value}")
109
+ console.print(Text(f"✅ Set {key} = {value}"))
108
110
 
109
111
 
110
112
  @config_group.command("get")
@@ -115,7 +117,7 @@ def get_config(key):
115
117
  config = load_config()
116
118
 
117
119
  if key not in config:
118
- console.print(f"[yellow]Configuration key '{key}' not found.[/yellow]")
120
+ console.print(Text(f"[yellow]Configuration key '{key}' not found.[/yellow]"))
119
121
  raise click.ClickException(f"Configuration key not found: {key}")
120
122
 
121
123
  value = config[key]
@@ -136,13 +138,13 @@ def unset_config(key):
136
138
  config = load_config()
137
139
 
138
140
  if key not in config:
139
- console.print(f"[yellow]Configuration key '{key}' not found.[/yellow]")
141
+ console.print(Text(f"[yellow]Configuration key '{key}' not found.[/yellow]"))
140
142
  return
141
143
 
142
144
  del config[key]
143
145
  save_config(config)
144
146
 
145
- console.print(f"✅ Removed {key} from configuration")
147
+ console.print(Text(f"✅ Removed {key} from configuration"))
146
148
 
147
149
 
148
150
  @config_group.command("reset")
@@ -168,13 +170,11 @@ def reset_config(force):
168
170
 
169
171
  def _configure_interactive():
170
172
  """Shared configuration logic for both configure commands."""
171
- console.print(
172
- Panel(
173
- "[bold cyan]AIP Configuration[/bold cyan]\nConfigure your AIP CLI settings.",
174
- title="🔧 Configuration Setup",
175
- border_style="cyan",
176
- )
173
+ # Display AIP welcome banner
174
+ branding = AIPBranding.create_from_sdk(
175
+ sdk_version=_SDK_VERSION, package_name="glaip-sdk"
177
176
  )
177
+ branding.display_welcome_panel(title="🔧 AIP Configuration")
178
178
 
179
179
  # Load existing config
180
180
  config = load_config()
@@ -208,7 +208,7 @@ def _configure_interactive():
208
208
  # Save configuration
209
209
  save_config(config)
210
210
 
211
- console.print(f"\n✅ Configuration saved to: {CONFIG_FILE}")
211
+ console.print(Text(f"\n✅ Configuration saved to: {CONFIG_FILE}"))
212
212
 
213
213
  # Test the new configuration
214
214
  console.print("\n🔌 Testing connection...")
@@ -219,9 +219,9 @@ def _configure_interactive():
219
219
  # Try to list resources to test connection
220
220
  try:
221
221
  agents = client.list_agents()
222
- console.print(f"✅ Connection successful! Found {len(agents)} agents")
222
+ console.print(Text(f"✅ Connection successful! Found {len(agents)} agents"))
223
223
  except Exception as e:
224
- console.print(f"⚠️ Connection established but API call failed: {e}")
224
+ console.print(Text(f"⚠️ Connection established but API call failed: {e}"))
225
225
  console.print(
226
226
  " You may need to check your API permissions or network access"
227
227
  )
@@ -229,7 +229,7 @@ def _configure_interactive():
229
229
  client.close()
230
230
 
231
231
  except Exception as e:
232
- console.print(f"❌ Connection failed: {e}")
232
+ console.print(Text(f"❌ Connection failed: {e}"))
233
233
  console.print(" Please check your API URL and key")
234
234
  console.print(" You can run 'aip status' later to test again")
235
235
 
@@ -5,20 +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
-
14
- from ..utils import (
12
+ from rich.text import Text
13
+
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 (
15
31
  coerce_to_row,
16
32
  get_client,
17
33
  output_flags,
18
34
  output_list,
19
35
  output_result,
20
- resolve_resource,
21
36
  )
37
+ from glaip_sdk.rich_components import AIPPanel
38
+ from glaip_sdk.utils import format_datetime
22
39
 
23
40
  console = Console()
24
41
 
@@ -31,12 +48,14 @@ def mcps_group():
31
48
 
32
49
  def _resolve_mcp(ctx, client, ref, select=None):
33
50
  """Resolve MCP reference (ID or name) with ambiguity handling."""
34
- return resolve_resource(
51
+ return resolve_resource_reference(
35
52
  ctx,
53
+ client,
36
54
  ref,
37
- get_by_id=client.mcps.get_mcp_by_id,
38
- find_by_name=client.mcps.find_mcps,
39
- label="MCP",
55
+ "mcp",
56
+ client.mcps.get_mcp_by_id,
57
+ client.mcps.find_mcps,
58
+ "MCP",
40
59
  select=select,
41
60
  )
42
61
 
@@ -105,55 +124,117 @@ def create(ctx, name, transport, description, config):
105
124
  config=mcp_config,
106
125
  )
107
126
 
108
- view = (ctx.obj or {}).get("view", "rich")
109
- if view == "json":
110
- click.echo(json.dumps(mcp.model_dump(), indent=2))
111
- else:
112
- # Rich output
113
- panel = Panel(
114
- f"[green]✅ MCP '{mcp.name}' created successfully![/green]\n\n"
115
- f"ID: {mcp.id}\n"
116
- f"Type: server (default)\n"
117
- f"Description: {description or 'No description'}",
118
- title="🔌 MCP Created",
119
- border_style="green",
120
- )
121
- 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)
122
140
 
123
141
  except Exception as e:
124
- view = (ctx.obj or {}).get("view", "rich")
125
- if view == "json":
126
- click.echo(json.dumps({"error": str(e)}, indent=2))
127
- else:
128
- console.print(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")
129
145
  raise click.ClickException(str(e))
130
146
 
131
147
 
132
148
  @mcps_group.command()
133
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
+ )
134
155
  @output_flags()
135
156
  @click.pass_context
136
- def get(ctx, mcp_ref):
137
- """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
+ """
138
165
  try:
139
166
  client = get_client(ctx)
140
167
 
141
168
  # Resolve MCP using helper function
142
169
  mcp = _resolve_mcp(ctx, client, mcp_ref)
143
170
 
144
- # Create result data with actual available fields
145
- result_data = {
146
- "id": str(getattr(mcp, "id", "N/A")),
147
- "name": getattr(mcp, "name", "N/A"),
148
- "type": getattr(mcp, "type", "N/A"),
149
- "config": getattr(mcp, "config", "N/A"),
150
- "status": getattr(mcp, "status", "N/A"),
151
- "connection_status": getattr(mcp, "connection_status", "N/A"),
152
- }
153
-
154
- output_result(
155
- ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
156
- )
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
+ )
157
238
 
158
239
  except Exception as e:
159
240
  raise click.ClickException(str(e))
@@ -199,18 +280,17 @@ def list_tools(ctx, mcp_ref):
199
280
  raise click.ClickException(str(e))
200
281
 
201
282
 
202
- @mcps_group.command("tools-from-config")
283
+ @mcps_group.command("connect")
203
284
  @click.option(
204
285
  "--from-file",
205
286
  "config_file",
206
- type=click.Path(exists=True),
207
287
  required=True,
208
288
  help="MCP config JSON file",
209
289
  )
210
290
  @output_flags()
211
291
  @click.pass_context
212
- def tools_from_config(ctx, config_file):
213
- """Fetch tools from MCP config."""
292
+ def connect(ctx, config_file):
293
+ """Connect to MCP using config file."""
214
294
  try:
215
295
  client = get_client(ctx)
216
296
 
@@ -221,64 +301,9 @@ def tools_from_config(ctx, config_file):
221
301
  view = (ctx.obj or {}).get("view", "rich")
222
302
  if view != "json":
223
303
  console.print(
224
- f"[yellow]Fetching tools from MCP config in {config_file}...[/yellow]"
225
- )
226
-
227
- # Get tools from MCP config
228
- tools = client.mcps.get_mcp_tools_from_config(config)
229
-
230
- view = (ctx.obj or {}).get("view", "rich")
231
- if view == "json":
232
- click.echo(json.dumps(tools, indent=2))
233
- else: # rich output
234
- if tools:
235
- table = Table(
236
- title="🔧 Tools from MCP Config",
237
- show_header=True,
238
- header_style="bold magenta",
304
+ Text(
305
+ f"[yellow]Connecting to MCP with config from {config_file}...[/yellow]"
239
306
  )
240
- table.add_column("Name", style="cyan", no_wrap=True)
241
- table.add_column("Description", style="green")
242
- table.add_column("Type", style="yellow")
243
-
244
- for tool in tools:
245
- table.add_row(
246
- tool.get("name", "N/A"),
247
- tool.get("description", "N/A")[:50] + "..."
248
- if len(tool.get("description", "")) > 50
249
- else tool.get("description", "N/A"),
250
- tool.get("type", "N/A"),
251
- )
252
- console.print(table)
253
- else:
254
- console.print("[yellow]No tools found in MCP config[/yellow]")
255
-
256
- except Exception as e:
257
- raise click.ClickException(str(e))
258
-
259
-
260
- @mcps_group.command("test-connection")
261
- @click.option(
262
- "--from-file",
263
- "config_file",
264
- required=True,
265
- help="MCP config JSON file",
266
- )
267
- @output_flags()
268
- @click.pass_context
269
- def test_connection(ctx, config_file):
270
- """Test MCP connection using config file."""
271
- try:
272
- client = get_client(ctx)
273
-
274
- # Load MCP config from file
275
- with open(config_file) as f:
276
- config = json.load(f)
277
-
278
- view = (ctx.obj or {}).get("view", "rich")
279
- if view != "json":
280
- console.print(
281
- f"[yellow]Testing MCP connection with config from {config_file}...[/yellow]"
282
307
  )
283
308
 
284
309
  # Test connection using config
@@ -286,12 +311,12 @@ def test_connection(ctx, config_file):
286
311
 
287
312
  view = (ctx.obj or {}).get("view", "rich")
288
313
  if view == "json":
289
- click.echo(json.dumps(result, indent=2))
314
+ handle_json_output(ctx, result)
290
315
  else:
291
- success_panel = Panel(
292
- f"[green]✓[/green] MCP connection test successful!\n\n"
316
+ success_panel = AIPPanel(
317
+ f"[green]✓[/green] MCP connection successful!\n\n"
293
318
  f"[bold]Result:[/bold] {result}",
294
- title="🔌 Connection Test",
319
+ title="🔌 Connection",
295
320
  border_style="green",
296
321
  )
297
322
  console.print(success_panel)
@@ -330,23 +355,16 @@ def update(ctx, mcp_ref, name, description, config):
330
355
  if not update_data:
331
356
  raise click.ClickException("No update fields specified")
332
357
 
333
- # Update MCP
358
+ # Update MCP (automatically chooses PUT or PATCH based on provided fields)
334
359
  updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
335
360
 
336
- view = (ctx.obj or {}).get("view", "rich")
337
- if view == "json":
338
- click.echo(json.dumps(updated_mcp.model_dump(), indent=2))
339
- else:
340
- console.print(
341
- f"[green]✅ MCP '{updated_mcp.name}' updated successfully[/green]"
342
- )
361
+ handle_json_output(ctx, updated_mcp.model_dump())
362
+ handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
343
363
 
344
364
  except Exception as e:
345
- view = (ctx.obj or {}).get("view", "rich")
346
- if view == "json":
347
- click.echo(json.dumps({"error": str(e)}, indent=2))
348
- else:
349
- console.print(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")
350
368
  raise click.ClickException(str(e))
351
369
 
352
370
 
@@ -364,30 +382,22 @@ def delete(ctx, mcp_ref, yes):
364
382
  mcp = _resolve_mcp(ctx, client, mcp_ref)
365
383
 
366
384
  # Confirm deletion
367
- if not yes and not click.confirm(
368
- f"Are you sure you want to delete MCP '{mcp.name}'?"
369
- ):
370
- view = (ctx.obj or {}).get("view", "rich")
371
- if view != "json":
372
- console.print("Deletion cancelled.")
385
+ if not yes and not display_confirmation_prompt("MCP", mcp.name):
373
386
  return
374
387
 
375
388
  client.mcps.delete_mcp(mcp.id)
376
389
 
377
- view = (ctx.obj or {}).get("view", "rich")
378
- if view == "json":
379
- click.echo(
380
- json.dumps(
381
- {"success": True, "message": f"MCP '{mcp.name}' deleted"}, indent=2
382
- )
383
- )
384
- else:
385
- console.print(f"[green]✅ MCP '{mcp.name}' deleted successfully[/green]")
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))
386
398
 
387
399
  except Exception as e:
388
- view = (ctx.obj or {}).get("view", "rich")
389
- if view == "json":
390
- click.echo(json.dumps({"error": str(e)}, indent=2))
391
- else:
392
- console.print(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")
393
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