glaip-sdk 0.0.2__py3-none-any.whl → 0.0.4__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 (40) hide show
  1. glaip_sdk/__init__.py +2 -2
  2. glaip_sdk/_version.py +51 -0
  3. glaip_sdk/branding.py +145 -0
  4. glaip_sdk/cli/commands/agents.py +876 -166
  5. glaip_sdk/cli/commands/configure.py +46 -104
  6. glaip_sdk/cli/commands/init.py +43 -118
  7. glaip_sdk/cli/commands/mcps.py +86 -161
  8. glaip_sdk/cli/commands/tools.py +196 -57
  9. glaip_sdk/cli/main.py +43 -29
  10. glaip_sdk/cli/utils.py +258 -27
  11. glaip_sdk/client/__init__.py +54 -2
  12. glaip_sdk/client/agents.py +196 -237
  13. glaip_sdk/client/base.py +62 -2
  14. glaip_sdk/client/mcps.py +63 -20
  15. glaip_sdk/client/tools.py +236 -81
  16. glaip_sdk/config/constants.py +10 -3
  17. glaip_sdk/exceptions.py +13 -0
  18. glaip_sdk/models.py +21 -5
  19. glaip_sdk/utils/__init__.py +116 -18
  20. glaip_sdk/utils/client_utils.py +284 -0
  21. glaip_sdk/utils/rendering/__init__.py +1 -0
  22. glaip_sdk/utils/rendering/formatting.py +211 -0
  23. glaip_sdk/utils/rendering/models.py +53 -0
  24. glaip_sdk/utils/rendering/renderer/__init__.py +38 -0
  25. glaip_sdk/utils/rendering/renderer/base.py +827 -0
  26. glaip_sdk/utils/rendering/renderer/config.py +33 -0
  27. glaip_sdk/utils/rendering/renderer/console.py +54 -0
  28. glaip_sdk/utils/rendering/renderer/debug.py +82 -0
  29. glaip_sdk/utils/rendering/renderer/panels.py +123 -0
  30. glaip_sdk/utils/rendering/renderer/progress.py +118 -0
  31. glaip_sdk/utils/rendering/renderer/stream.py +198 -0
  32. glaip_sdk/utils/rendering/steps.py +168 -0
  33. glaip_sdk/utils/run_renderer.py +22 -1086
  34. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/METADATA +8 -36
  35. glaip_sdk-0.0.4.dist-info/RECORD +41 -0
  36. glaip_sdk/cli/config.py +0 -592
  37. glaip_sdk/utils.py +0 -167
  38. glaip_sdk-0.0.2.dist-info/RECORD +0 -28
  39. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/WHEEL +0 -0
  40. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/entry_points.txt +0 -0
@@ -10,15 +10,15 @@ import click
10
10
  from rich.console import Console
11
11
  from rich.panel import Panel
12
12
  from rich.table import Table
13
-
14
- from glaip_sdk.utils import is_uuid
13
+ from rich.text import Text
15
14
 
16
15
  from ..utils import (
16
+ coerce_to_row,
17
17
  get_client,
18
- handle_ambiguous_resource,
19
18
  output_flags,
20
19
  output_list,
21
20
  output_result,
21
+ resolve_resource,
22
22
  )
23
23
 
24
24
  console = Console()
@@ -32,25 +32,14 @@ def mcps_group():
32
32
 
33
33
  def _resolve_mcp(ctx, client, ref, select=None):
34
34
  """Resolve MCP reference (ID or name) with ambiguity handling."""
35
- if is_uuid(ref):
36
- return client.mcps.get_mcp_by_id(ref)
37
-
38
- # Find MCPs by name
39
- matches = client.mcps.find_mcps(name=ref)
40
- if not matches:
41
- raise click.ClickException(f"MCP '{ref}' not found")
42
-
43
- if len(matches) == 1:
44
- return matches[0]
45
-
46
- # Multiple matches - handle ambiguity
47
- if select:
48
- idx = int(select) - 1
49
- if not (0 <= idx < len(matches)):
50
- raise click.ClickException(f"--select must be 1..{len(matches)}")
51
- return matches[idx]
52
-
53
- return handle_ambiguous_resource(ctx, "MCP", ref, matches)
35
+ return resolve_resource(
36
+ ctx,
37
+ ref,
38
+ get_by_id=client.mcps.get_mcp_by_id,
39
+ find_by_name=client.mcps.find_mcps,
40
+ label="MCP",
41
+ select=select,
42
+ )
54
43
 
55
44
 
56
45
  @mcps_group.command(name="list")
@@ -71,24 +60,17 @@ def list_mcps(ctx):
71
60
 
72
61
  # Transform function for safe dictionary access
73
62
  def transform_mcp(mcp):
74
- # Handle both dict and object formats
75
- if isinstance(mcp, dict):
76
- return {
77
- "id": str(mcp.get("id", "N/A")),
78
- "name": mcp.get("name", "N/A"),
79
- "config": str(mcp.get("config", "N/A"))[:50] + "..."
80
- if mcp.get("config")
81
- else "N/A",
82
- }
83
- else:
84
- # Fallback to attribute access
85
- return {
86
- "id": str(getattr(mcp, "id", "N/A")),
87
- "name": getattr(mcp, "name", "N/A"),
88
- "config": str(getattr(mcp, "config", "N/A"))[:50] + "..."
89
- if getattr(mcp, "config", None)
90
- else "N/A",
91
- }
63
+ row = coerce_to_row(mcp, ["id", "name", "config"])
64
+ # Ensure id is always a string
65
+ row["id"] = str(row["id"])
66
+ # Truncate config field for display
67
+ if row["config"] != "N/A":
68
+ row["config"] = (
69
+ str(row["config"])[:50] + "..."
70
+ if len(str(row["config"])) > 50
71
+ else str(row["config"])
72
+ )
73
+ return row
92
74
 
93
75
  output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
94
76
 
@@ -118,13 +100,14 @@ def create(ctx, name, transport, description, config):
118
100
 
119
101
  mcp = client.mcps.create_mcp(
120
102
  name=name,
121
- type="server", # Always server type
103
+ type="server", # MCPs are always server type
122
104
  transport=transport,
123
105
  description=description,
124
106
  config=mcp_config,
125
107
  )
126
108
 
127
- if ctx.obj.get("view") == "json":
109
+ view = (ctx.obj or {}).get("view", "rich")
110
+ if view == "json":
128
111
  click.echo(json.dumps(mcp.model_dump(), indent=2))
129
112
  else:
130
113
  # Rich output
@@ -139,10 +122,11 @@ def create(ctx, name, transport, description, config):
139
122
  console.print(panel)
140
123
 
141
124
  except Exception as e:
142
- if ctx.obj.get("view") == "json":
125
+ view = (ctx.obj or {}).get("view", "rich")
126
+ if view == "json":
143
127
  click.echo(json.dumps({"error": str(e)}, indent=2))
144
128
  else:
145
- console.print(f"[red]Error creating MCP: {e}[/red]")
129
+ console.print(Text(f"[red]Error creating MCP: {e}[/red]"))
146
130
  raise click.ClickException(str(e))
147
131
 
148
132
 
@@ -155,25 +139,8 @@ def get(ctx, mcp_ref):
155
139
  try:
156
140
  client = get_client(ctx)
157
141
 
158
- # Get MCP by ID or name (shows all matches for names)
159
- if is_uuid(mcp_ref):
160
- mcp = client.mcps.get_mcp_by_id(mcp_ref)
161
- else:
162
- # Find MCPs by name
163
- matches = client.mcps.find_mcps(name=mcp_ref)
164
- if not matches:
165
- raise click.ClickException(f"MCP '{mcp_ref}' not found")
166
- elif len(matches) == 1:
167
- mcp = matches[0]
168
- else:
169
- # Show all matches
170
- console.print(
171
- f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
172
- )
173
- for i, match in enumerate(matches, 1):
174
- console.print(f" {i}. {match.name} (ID: {match.id})")
175
- console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
176
- return
142
+ # Resolve MCP using helper function
143
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
177
144
 
178
145
  # Create result data with actual available fields
179
146
  result_data = {
@@ -202,53 +169,32 @@ def list_tools(ctx, mcp_ref):
202
169
  try:
203
170
  client = get_client(ctx)
204
171
 
205
- # Get MCP by ID or name (shows all matches for names)
206
- if is_uuid(mcp_ref):
207
- mcp = client.get_mcp_by_id(mcp_ref)
208
- else:
209
- # Find MCPs by name
210
- matches = client.find_mcps(name=mcp_ref)
211
- if not matches:
212
- raise click.ClickException(f"MCP '{mcp_ref}' not found")
213
- elif len(matches) == 1:
214
- mcp = matches[0]
215
- else:
216
- # Show all matches
217
- console.print(
218
- f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
219
- )
220
- for i, match in enumerate(matches, 1):
221
- console.print(f" {i}. {match.name} (ID: {match.id})")
222
- console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
223
- return
172
+ # Resolve MCP using helper function
173
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
224
174
 
225
175
  # Get tools from MCP
226
176
  tools = client.mcps.get_mcp_tools(mcp.id)
227
177
 
228
- if ctx.obj.get("view") == "json":
229
- click.echo(json.dumps(tools, indent=2))
230
- else: # rich output
231
- if tools:
232
- table = Table(
233
- title=f"🔧 Tools from MCP: {mcp.name}",
234
- show_header=True,
235
- header_style="bold magenta",
236
- )
237
- table.add_column("Name", style="cyan", no_wrap=True)
238
- table.add_column("Description", style="green")
239
- table.add_column("Type", style="yellow")
178
+ # Define table columns: (data_key, header, style, width)
179
+ columns = [
180
+ ("name", "Name", "cyan", None),
181
+ ("description", "Description", "green", 50),
182
+ ("type", "Type", "yellow", None),
183
+ ]
240
184
 
241
- for tool in tools:
242
- table.add_row(
243
- tool.get("name", "N/A"),
244
- tool.get("description", "N/A")[:50] + "..."
245
- if len(tool.get("description", "")) > 50
246
- else tool.get("description", "N/A"),
247
- tool.get("type", "N/A"),
248
- )
249
- console.print(table)
250
- else:
251
- console.print(f"[yellow]No tools found in MCP '{mcp.name}'[/yellow]")
185
+ # Transform function for safe dictionary access
186
+ def transform_tool(tool):
187
+ return {
188
+ "name": tool.get("name", "N/A"),
189
+ "description": tool.get("description", "N/A")[:47] + "..."
190
+ if len(tool.get("description", "")) > 47
191
+ else tool.get("description", "N/A"),
192
+ "type": tool.get("type", "N/A"),
193
+ }
194
+
195
+ output_list(
196
+ ctx, tools, f"🔧 Tools from MCP: {mcp.name}", columns, transform_tool
197
+ )
252
198
 
253
199
  except Exception as e:
254
200
  raise click.ClickException(str(e))
@@ -273,15 +219,17 @@ def tools_from_config(ctx, config_file):
273
219
  with open(config_file) as f:
274
220
  config = json.load(f)
275
221
 
276
- if ctx.obj.get("view") != "json":
222
+ view = (ctx.obj or {}).get("view", "rich")
223
+ if view != "json":
277
224
  console.print(
278
225
  f"[yellow]Fetching tools from MCP config in {config_file}...[/yellow]"
279
226
  )
280
227
 
281
228
  # Get tools from MCP config
282
- tools = client.mcps.get_tools_from_mcp_config(config)
229
+ tools = client.mcps.get_mcp_tools_from_config(config)
283
230
 
284
- if ctx.obj.get("view") == "json":
231
+ view = (ctx.obj or {}).get("view", "rich")
232
+ if view == "json":
285
233
  click.echo(json.dumps(tools, indent=2))
286
234
  else: # rich output
287
235
  if tools:
@@ -304,7 +252,7 @@ def tools_from_config(ctx, config_file):
304
252
  )
305
253
  console.print(table)
306
254
  else:
307
- console.print("[yellow]No tools found in MCP config[/yellow]")
255
+ console.print(Text("[yellow]No tools found in MCP config[/yellow]"))
308
256
 
309
257
  except Exception as e:
310
258
  raise click.ClickException(str(e))
@@ -328,15 +276,19 @@ def test_connection(ctx, config_file):
328
276
  with open(config_file) as f:
329
277
  config = json.load(f)
330
278
 
331
- if ctx.obj.get("view") != "json":
279
+ view = (ctx.obj or {}).get("view", "rich")
280
+ if view != "json":
332
281
  console.print(
333
- f"[yellow]Testing MCP connection with config from {config_file}...[/yellow]"
282
+ Text(
283
+ f"[yellow]Testing MCP connection with config from {config_file}...[/yellow]"
284
+ )
334
285
  )
335
286
 
336
287
  # Test connection using config
337
288
  result = client.mcps.test_mcp_connection_from_config(config)
338
289
 
339
- if ctx.obj.get("view") == "json":
290
+ view = (ctx.obj or {}).get("view", "rich")
291
+ if view == "json":
340
292
  click.echo(json.dumps(result, indent=2))
341
293
  else:
342
294
  success_panel = Panel(
@@ -363,25 +315,8 @@ def update(ctx, mcp_ref, name, description, config):
363
315
  try:
364
316
  client = get_client(ctx)
365
317
 
366
- # Get MCP by ID or name (shows all matches for names)
367
- if is_uuid(mcp_ref):
368
- mcp = client.mcps.get_mcp_by_id(mcp_ref)
369
- else:
370
- # Find MCPs by name
371
- matches = client.mcps.find_mcps(name=mcp_ref)
372
- if not matches:
373
- raise click.ClickException(f"MCP '{mcp_ref}' not found")
374
- elif len(matches) == 1:
375
- mcp = matches[0]
376
- else:
377
- # Show all matches
378
- console.print(
379
- f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
380
- )
381
- for i, match in enumerate(matches, 1):
382
- console.print(f" {i}. {match.name} (ID: {match.id})")
383
- console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
384
- return
318
+ # Resolve MCP using helper function
319
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
385
320
 
386
321
  # Build update data
387
322
  update_data = {}
@@ -399,20 +334,22 @@ def update(ctx, mcp_ref, name, description, config):
399
334
  raise click.ClickException("No update fields specified")
400
335
 
401
336
  # Update MCP
402
- updated_mcp = client.mcps.update_mcp(mcp.id, update_data)
337
+ updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
403
338
 
404
- if ctx.obj.get("view") == "json":
339
+ view = (ctx.obj or {}).get("view", "rich")
340
+ if view == "json":
405
341
  click.echo(json.dumps(updated_mcp.model_dump(), indent=2))
406
342
  else:
407
343
  console.print(
408
- f"[green]✅ MCP '{updated_mcp.name}' updated successfully[/green]"
344
+ Text(f"[green]✅ MCP '{updated_mcp.name}' updated successfully[/green]")
409
345
  )
410
346
 
411
347
  except Exception as e:
412
- if ctx.obj.get("view") == "json":
348
+ view = (ctx.obj or {}).get("view", "rich")
349
+ if view == "json":
413
350
  click.echo(json.dumps({"error": str(e)}, indent=2))
414
351
  else:
415
- console.print(f"[red]Error updating MCP: {e}[/red]")
352
+ console.print(Text(f"[red]Error updating MCP: {e}[/red]"))
416
353
  raise click.ClickException(str(e))
417
354
 
418
355
 
@@ -426,48 +363,36 @@ def delete(ctx, mcp_ref, yes):
426
363
  try:
427
364
  client = get_client(ctx)
428
365
 
429
- # Get MCP by ID or name (shows all matches for names)
430
- if is_uuid(mcp_ref):
431
- mcp = client.mcps.get_mcp_by_id(mcp_ref)
432
- else:
433
- # Find MCPs by name
434
- matches = client.mcps.find_mcps(name=mcp_ref)
435
- if not matches:
436
- raise click.ClickException(f"MCP '{mcp_ref}' not found")
437
- elif len(matches) == 1:
438
- mcp = matches[0]
439
- else:
440
- # Show all matches
441
- console.print(
442
- f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
443
- )
444
- for i, match in enumerate(matches, 1):
445
- console.print(f" {i}. {match.name} (ID: {match.id})")
446
- console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
447
- return
366
+ # Resolve MCP using helper function
367
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
448
368
 
449
369
  # Confirm deletion
450
370
  if not yes and not click.confirm(
451
371
  f"Are you sure you want to delete MCP '{mcp.name}'?"
452
372
  ):
453
- if ctx.obj.get("view") != "json":
454
- console.print("Deletion cancelled.")
373
+ view = (ctx.obj or {}).get("view", "rich")
374
+ if view != "json":
375
+ console.print(Text("Deletion cancelled."))
455
376
  return
456
377
 
457
378
  client.mcps.delete_mcp(mcp.id)
458
379
 
459
- if ctx.obj.get("view") == "json":
380
+ view = (ctx.obj or {}).get("view", "rich")
381
+ if view == "json":
460
382
  click.echo(
461
383
  json.dumps(
462
384
  {"success": True, "message": f"MCP '{mcp.name}' deleted"}, indent=2
463
385
  )
464
386
  )
465
387
  else:
466
- console.print(f"[green]✅ MCP '{mcp.name}' deleted successfully[/green]")
388
+ console.print(
389
+ Text(f"[green]✅ MCP '{mcp.name}' deleted successfully[/green]")
390
+ )
467
391
 
468
392
  except Exception as e:
469
- if ctx.obj.get("view") == "json":
393
+ view = (ctx.obj or {}).get("view", "rich")
394
+ if view == "json":
470
395
  click.echo(json.dumps({"error": str(e)}, indent=2))
471
396
  else:
472
- console.print(f"[red]Error deleting MCP: {e}[/red]")
397
+ console.print(Text(f"[red]Error deleting MCP: {e}[/red]"))
473
398
  raise click.ClickException(str(e))