glaip-sdk 0.0.1b10__py3-none-any.whl → 0.0.3__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 (39) hide show
  1. glaip_sdk/__init__.py +2 -2
  2. glaip_sdk/_version.py +51 -0
  3. glaip_sdk/cli/commands/agents.py +201 -109
  4. glaip_sdk/cli/commands/configure.py +29 -87
  5. glaip_sdk/cli/commands/init.py +16 -7
  6. glaip_sdk/cli/commands/mcps.py +73 -153
  7. glaip_sdk/cli/commands/tools.py +185 -49
  8. glaip_sdk/cli/main.py +30 -27
  9. glaip_sdk/cli/utils.py +126 -13
  10. glaip_sdk/client/__init__.py +54 -2
  11. glaip_sdk/client/agents.py +175 -237
  12. glaip_sdk/client/base.py +62 -2
  13. glaip_sdk/client/mcps.py +63 -20
  14. glaip_sdk/client/tools.py +95 -28
  15. glaip_sdk/config/constants.py +10 -3
  16. glaip_sdk/exceptions.py +13 -0
  17. glaip_sdk/models.py +20 -4
  18. glaip_sdk/utils/__init__.py +116 -18
  19. glaip_sdk/utils/client_utils.py +284 -0
  20. glaip_sdk/utils/rendering/__init__.py +1 -0
  21. glaip_sdk/utils/rendering/formatting.py +211 -0
  22. glaip_sdk/utils/rendering/models.py +53 -0
  23. glaip_sdk/utils/rendering/renderer/__init__.py +38 -0
  24. glaip_sdk/utils/rendering/renderer/base.py +827 -0
  25. glaip_sdk/utils/rendering/renderer/config.py +33 -0
  26. glaip_sdk/utils/rendering/renderer/console.py +54 -0
  27. glaip_sdk/utils/rendering/renderer/debug.py +82 -0
  28. glaip_sdk/utils/rendering/renderer/panels.py +123 -0
  29. glaip_sdk/utils/rendering/renderer/progress.py +118 -0
  30. glaip_sdk/utils/rendering/renderer/stream.py +198 -0
  31. glaip_sdk/utils/rendering/steps.py +168 -0
  32. glaip_sdk/utils/run_renderer.py +22 -1086
  33. {glaip_sdk-0.0.1b10.dist-info → glaip_sdk-0.0.3.dist-info}/METADATA +9 -37
  34. glaip_sdk-0.0.3.dist-info/RECORD +40 -0
  35. glaip_sdk/cli/config.py +0 -592
  36. glaip_sdk/utils.py +0 -167
  37. glaip_sdk-0.0.1b10.dist-info/RECORD +0 -28
  38. {glaip_sdk-0.0.1b10.dist-info → glaip_sdk-0.0.3.dist-info}/WHEEL +0 -0
  39. {glaip_sdk-0.0.1b10.dist-info → glaip_sdk-0.0.3.dist-info}/entry_points.txt +0 -0
@@ -13,6 +13,8 @@ import yaml
13
13
  from rich.console import Console
14
14
  from rich.panel import Panel
15
15
 
16
+ from glaip_sdk import Client
17
+
16
18
  console = Console()
17
19
 
18
20
 
@@ -50,14 +52,22 @@ def init_command(no_scaffold, no_demo):
50
52
 
51
53
  # Create config directory
52
54
  config_dir = Path.home() / ".aip"
53
- config_dir.mkdir(exist_ok=True)
55
+ try:
56
+ config_dir.mkdir(exist_ok=True)
57
+ except Exception as e:
58
+ console.print(f"⚠️ Warning: Could not create config directory: {e}")
59
+ return
54
60
 
55
61
  # Save configuration
56
62
  config = {"api_url": api_url, "api_key": api_key}
57
63
 
58
64
  config_file = config_dir / "config.yaml"
59
- with open(config_file, "w") as f:
60
- yaml.dump(config, f, default_flow_style=False)
65
+ try:
66
+ with open(config_file, "w") as f:
67
+ yaml.dump(config, f, default_flow_style=False)
68
+ except Exception as e:
69
+ console.print(f"⚠️ Warning: Could not save configuration: {e}")
70
+ return
61
71
 
62
72
  # Set secure file permissions (0600) - best effort on all platforms
63
73
  try:
@@ -77,8 +87,6 @@ def init_command(no_scaffold, no_demo):
77
87
  create_sample_resources = input("> ").strip().lower()
78
88
  if create_sample_resources in ["", "y", "yes"]:
79
89
  try:
80
- from glaip_sdk import Client
81
-
82
90
  # Set environment variables
83
91
  os.environ["AIP_API_URL"] = api_url
84
92
  os.environ["AIP_API_KEY"] = api_key
@@ -127,8 +135,6 @@ def init_command(no_scaffold, no_demo):
127
135
  def launch_interactive_demo():
128
136
  """Launch interactive demo with sample agent."""
129
137
  try:
130
- from glaip_sdk import Client
131
-
132
138
  console.print(
133
139
  Panel(
134
140
  "[bold green]Interactive Demo[/bold green]\nType to talk to hello-world. Ctrl+C to exit.",
@@ -152,6 +158,9 @@ def launch_interactive_demo():
152
158
  if user_input.lower() in ["exit", "quit", "bye"]:
153
159
  break
154
160
 
161
+ if not user_input: # Skip empty inputs
162
+ continue
163
+
155
164
  response = agent.run(user_input)
156
165
  console.print(f"🤖 {response}")
157
166
 
@@ -11,14 +11,13 @@ from rich.console import Console
11
11
  from rich.panel import Panel
12
12
  from rich.table import Table
13
13
 
14
- from glaip_sdk.utils import is_uuid
15
-
16
14
  from ..utils import (
15
+ coerce_to_row,
17
16
  get_client,
18
- handle_ambiguous_resource,
19
17
  output_flags,
20
18
  output_list,
21
19
  output_result,
20
+ resolve_resource,
22
21
  )
23
22
 
24
23
  console = Console()
@@ -32,25 +31,14 @@ def mcps_group():
32
31
 
33
32
  def _resolve_mcp(ctx, client, ref, select=None):
34
33
  """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)
34
+ return resolve_resource(
35
+ ctx,
36
+ ref,
37
+ get_by_id=client.mcps.get_mcp_by_id,
38
+ find_by_name=client.mcps.find_mcps,
39
+ label="MCP",
40
+ select=select,
41
+ )
54
42
 
55
43
 
56
44
  @mcps_group.command(name="list")
@@ -71,24 +59,17 @@ def list_mcps(ctx):
71
59
 
72
60
  # Transform function for safe dictionary access
73
61
  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
- }
62
+ row = coerce_to_row(mcp, ["id", "name", "config"])
63
+ # Ensure id is always a string
64
+ row["id"] = str(row["id"])
65
+ # Truncate config field for display
66
+ if row["config"] != "N/A":
67
+ row["config"] = (
68
+ str(row["config"])[:50] + "..."
69
+ if len(str(row["config"])) > 50
70
+ else str(row["config"])
71
+ )
72
+ return row
92
73
 
93
74
  output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
94
75
 
@@ -118,13 +99,14 @@ def create(ctx, name, transport, description, config):
118
99
 
119
100
  mcp = client.mcps.create_mcp(
120
101
  name=name,
121
- type="server", # Always server type
102
+ type="server", # MCPs are always server type
122
103
  transport=transport,
123
104
  description=description,
124
105
  config=mcp_config,
125
106
  )
126
107
 
127
- if ctx.obj.get("view") == "json":
108
+ view = (ctx.obj or {}).get("view", "rich")
109
+ if view == "json":
128
110
  click.echo(json.dumps(mcp.model_dump(), indent=2))
129
111
  else:
130
112
  # Rich output
@@ -139,7 +121,8 @@ def create(ctx, name, transport, description, config):
139
121
  console.print(panel)
140
122
 
141
123
  except Exception as e:
142
- if ctx.obj.get("view") == "json":
124
+ view = (ctx.obj or {}).get("view", "rich")
125
+ if view == "json":
143
126
  click.echo(json.dumps({"error": str(e)}, indent=2))
144
127
  else:
145
128
  console.print(f"[red]Error creating MCP: {e}[/red]")
@@ -155,25 +138,8 @@ def get(ctx, mcp_ref):
155
138
  try:
156
139
  client = get_client(ctx)
157
140
 
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
141
+ # Resolve MCP using helper function
142
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
177
143
 
178
144
  # Create result data with actual available fields
179
145
  result_data = {
@@ -202,53 +168,32 @@ def list_tools(ctx, mcp_ref):
202
168
  try:
203
169
  client = get_client(ctx)
204
170
 
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
171
+ # Resolve MCP using helper function
172
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
224
173
 
225
174
  # Get tools from MCP
226
175
  tools = client.mcps.get_mcp_tools(mcp.id)
227
176
 
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")
177
+ # Define table columns: (data_key, header, style, width)
178
+ columns = [
179
+ ("name", "Name", "cyan", None),
180
+ ("description", "Description", "green", 50),
181
+ ("type", "Type", "yellow", None),
182
+ ]
240
183
 
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]")
184
+ # Transform function for safe dictionary access
185
+ def transform_tool(tool):
186
+ return {
187
+ "name": tool.get("name", "N/A"),
188
+ "description": tool.get("description", "N/A")[:47] + "..."
189
+ if len(tool.get("description", "")) > 47
190
+ else tool.get("description", "N/A"),
191
+ "type": tool.get("type", "N/A"),
192
+ }
193
+
194
+ output_list(
195
+ ctx, tools, f"🔧 Tools from MCP: {mcp.name}", columns, transform_tool
196
+ )
252
197
 
253
198
  except Exception as e:
254
199
  raise click.ClickException(str(e))
@@ -273,15 +218,17 @@ def tools_from_config(ctx, config_file):
273
218
  with open(config_file) as f:
274
219
  config = json.load(f)
275
220
 
276
- if ctx.obj.get("view") != "json":
221
+ view = (ctx.obj or {}).get("view", "rich")
222
+ if view != "json":
277
223
  console.print(
278
224
  f"[yellow]Fetching tools from MCP config in {config_file}...[/yellow]"
279
225
  )
280
226
 
281
227
  # Get tools from MCP config
282
- tools = client.mcps.get_tools_from_mcp_config(config)
228
+ tools = client.mcps.get_mcp_tools_from_config(config)
283
229
 
284
- if ctx.obj.get("view") == "json":
230
+ view = (ctx.obj or {}).get("view", "rich")
231
+ if view == "json":
285
232
  click.echo(json.dumps(tools, indent=2))
286
233
  else: # rich output
287
234
  if tools:
@@ -328,7 +275,8 @@ def test_connection(ctx, config_file):
328
275
  with open(config_file) as f:
329
276
  config = json.load(f)
330
277
 
331
- if ctx.obj.get("view") != "json":
278
+ view = (ctx.obj or {}).get("view", "rich")
279
+ if view != "json":
332
280
  console.print(
333
281
  f"[yellow]Testing MCP connection with config from {config_file}...[/yellow]"
334
282
  )
@@ -336,7 +284,8 @@ def test_connection(ctx, config_file):
336
284
  # Test connection using config
337
285
  result = client.mcps.test_mcp_connection_from_config(config)
338
286
 
339
- if ctx.obj.get("view") == "json":
287
+ view = (ctx.obj or {}).get("view", "rich")
288
+ if view == "json":
340
289
  click.echo(json.dumps(result, indent=2))
341
290
  else:
342
291
  success_panel = Panel(
@@ -363,25 +312,8 @@ def update(ctx, mcp_ref, name, description, config):
363
312
  try:
364
313
  client = get_client(ctx)
365
314
 
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
315
+ # Resolve MCP using helper function
316
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
385
317
 
386
318
  # Build update data
387
319
  update_data = {}
@@ -399,9 +331,10 @@ def update(ctx, mcp_ref, name, description, config):
399
331
  raise click.ClickException("No update fields specified")
400
332
 
401
333
  # Update MCP
402
- updated_mcp = client.mcps.update_mcp(mcp.id, update_data)
334
+ updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
403
335
 
404
- if ctx.obj.get("view") == "json":
336
+ view = (ctx.obj or {}).get("view", "rich")
337
+ if view == "json":
405
338
  click.echo(json.dumps(updated_mcp.model_dump(), indent=2))
406
339
  else:
407
340
  console.print(
@@ -409,7 +342,8 @@ def update(ctx, mcp_ref, name, description, config):
409
342
  )
410
343
 
411
344
  except Exception as e:
412
- if ctx.obj.get("view") == "json":
345
+ view = (ctx.obj or {}).get("view", "rich")
346
+ if view == "json":
413
347
  click.echo(json.dumps({"error": str(e)}, indent=2))
414
348
  else:
415
349
  console.print(f"[red]Error updating MCP: {e}[/red]")
@@ -426,37 +360,22 @@ def delete(ctx, mcp_ref, yes):
426
360
  try:
427
361
  client = get_client(ctx)
428
362
 
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
363
+ # Resolve MCP using helper function
364
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
448
365
 
449
366
  # Confirm deletion
450
367
  if not yes and not click.confirm(
451
368
  f"Are you sure you want to delete MCP '{mcp.name}'?"
452
369
  ):
453
- if ctx.obj.get("view") != "json":
370
+ view = (ctx.obj or {}).get("view", "rich")
371
+ if view != "json":
454
372
  console.print("Deletion cancelled.")
455
373
  return
456
374
 
457
375
  client.mcps.delete_mcp(mcp.id)
458
376
 
459
- if ctx.obj.get("view") == "json":
377
+ view = (ctx.obj or {}).get("view", "rich")
378
+ if view == "json":
460
379
  click.echo(
461
380
  json.dumps(
462
381
  {"success": True, "message": f"MCP '{mcp.name}' deleted"}, indent=2
@@ -466,7 +385,8 @@ def delete(ctx, mcp_ref, yes):
466
385
  console.print(f"[green]✅ MCP '{mcp.name}' deleted successfully[/green]")
467
386
 
468
387
  except Exception as e:
469
- if ctx.obj.get("view") == "json":
388
+ view = (ctx.obj or {}).get("view", "rich")
389
+ if view == "json":
470
390
  click.echo(json.dumps({"error": str(e)}, indent=2))
471
391
  else:
472
392
  console.print(f"[red]Error deleting MCP: {e}[/red]")