glaip-sdk 0.0.7__py3-none-any.whl → 0.0.9__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.
glaip_sdk/branding.py CHANGED
@@ -31,8 +31,8 @@ except Exception: # pragma: no cover
31
31
 
32
32
 
33
33
  # ---- minimal, readable styles (light blue + white theme) -----------------
34
- PRIMARY = "bright_blue" # Light blue for main elements
35
- BORDER = "bright_blue" # Light blue borders
34
+ PRIMARY = "#15a2d8" # GDP Labs brand blue
35
+ BORDER = PRIMARY # Keep borders aligned with brand tone
36
36
  TITLE_STYLE = f"bold {PRIMARY}"
37
37
  LABEL = "bold"
38
38
 
@@ -95,7 +95,7 @@ GDP Labs AI Agents Package
95
95
  # ---- public API -----------------------------------------------------------
96
96
  def get_welcome_banner(self) -> str:
97
97
  """Get AIP banner with version info."""
98
- banner = self.AIP_LOGO
98
+ banner = f"[{PRIMARY}]{self.AIP_LOGO}[/{PRIMARY}]"
99
99
  line = f"Version: {self.version}"
100
100
  banner = f"{banner}\n{line}"
101
101
  return banner
@@ -6,6 +6,7 @@ Authors:
6
6
 
7
7
  import json
8
8
  import os
9
+ from collections.abc import Mapping
9
10
  from pathlib import Path
10
11
  from typing import Any
11
12
 
@@ -53,6 +54,7 @@ from glaip_sdk.cli.utils import (
53
54
  output_flags,
54
55
  output_list,
55
56
  output_result,
57
+ spinner_context,
56
58
  )
57
59
  from glaip_sdk.cli.validators import (
58
60
  validate_agent_instruction_cli as validate_agent_instruction,
@@ -76,6 +78,91 @@ console = Console()
76
78
  AGENT_NOT_FOUND_ERROR = "Agent not found"
77
79
 
78
80
 
81
+ def _safe_agent_attribute(agent: Any, name: str) -> Any:
82
+ """Return attribute value for ``name`` while filtering Mock sentinels."""
83
+
84
+ try:
85
+ value = getattr(agent, name)
86
+ except Exception:
87
+ return None
88
+
89
+ if hasattr(value, "_mock_name"):
90
+ return None
91
+ return value
92
+
93
+
94
+ def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
95
+ """Convert a mapping-like candidate to a plain dict when possible."""
96
+
97
+ if candidate is None:
98
+ return None
99
+ if isinstance(candidate, Mapping):
100
+ return dict(candidate)
101
+ return None
102
+
103
+
104
+ def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
105
+ """Attempt to call the named method and coerce its output to a dict."""
106
+
107
+ method = getattr(agent, method_name, None)
108
+ if not callable(method):
109
+ return None
110
+ try:
111
+ candidate = method()
112
+ except Exception:
113
+ return None
114
+ return _coerce_mapping_candidate(candidate)
115
+
116
+
117
+ def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
118
+ """Try standard serialisation helpers to produce a mapping."""
119
+
120
+ for attr in ("model_dump", "dict", "to_dict"):
121
+ mapping = _call_agent_method(agent, attr)
122
+ if mapping is not None:
123
+ return mapping
124
+ return None
125
+
126
+
127
+ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
128
+ """Construct a minimal mapping from well-known agent attributes."""
129
+
130
+ fallback_fields = (
131
+ "id",
132
+ "name",
133
+ "instruction",
134
+ "description",
135
+ "model",
136
+ "agent_config",
137
+ "tools",
138
+ "agents",
139
+ "mcps",
140
+ "timeout",
141
+ )
142
+
143
+ fallback: dict[str, Any] = {}
144
+ for field in fallback_fields:
145
+ value = _safe_agent_attribute(agent, field)
146
+ if value is not None:
147
+ fallback[field] = value
148
+
149
+ return fallback or {"name": str(agent)}
150
+
151
+
152
+ def _prepare_agent_output(agent: Any) -> dict[str, Any]:
153
+ """Build a JSON-serialisable mapping for CLI output."""
154
+
155
+ method_mapping = _coerce_agent_via_methods(agent)
156
+ if method_mapping is not None:
157
+ return method_mapping
158
+
159
+ intrinsic = _coerce_mapping_candidate(agent)
160
+ if intrinsic is not None:
161
+ return intrinsic
162
+
163
+ return _build_fallback_agent_mapping(agent)
164
+
165
+
79
166
  def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
80
167
  """Fetch full agent details by ID to ensure all fields are populated."""
81
168
  try:
@@ -199,7 +286,12 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
199
286
  return
200
287
 
201
288
  # Try to fetch and format raw agent data first
202
- formatted_data = _fetch_and_format_raw_agent_data(client, agent)
289
+ with spinner_context(
290
+ ctx,
291
+ "[bold blue]Loading agent details…[/bold blue]",
292
+ console_override=console,
293
+ ):
294
+ formatted_data = _fetch_and_format_raw_agent_data(client, agent)
203
295
 
204
296
  if formatted_data:
205
297
  # Use raw API data - this preserves ALL fields including account_id
@@ -216,7 +308,12 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
216
308
  ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
217
309
  )
218
310
 
219
- result_data = _format_fallback_agent_data(client, agent)
311
+ with spinner_context(
312
+ ctx,
313
+ "[bold blue]Preparing fallback agent details…[/bold blue]",
314
+ console_override=console,
315
+ ):
316
+ result_data = _format_fallback_agent_data(client, agent)
220
317
 
221
318
  # Display using output_result
222
319
  output_result(
@@ -289,13 +386,18 @@ def list_agents(
289
386
  """List agents with optional filtering."""
290
387
  try:
291
388
  client = get_client(ctx)
292
- agents = client.agents.list_agents(
293
- agent_type=agent_type,
294
- framework=framework,
295
- name=name,
296
- version=version,
297
- sync_langflow_agents=sync_langflow,
298
- )
389
+ with spinner_context(
390
+ ctx,
391
+ "[bold blue]Fetching agents…[/bold blue]",
392
+ console_override=console,
393
+ ):
394
+ agents = client.agents.list_agents(
395
+ agent_type=agent_type,
396
+ framework=framework,
397
+ name=name,
398
+ version=version,
399
+ sync_langflow_agents=sync_langflow,
400
+ )
299
401
 
300
402
  # Define table columns: (data_key, header, style, width)
301
403
  columns = [
@@ -364,7 +466,12 @@ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> Non
364
466
 
365
467
  # Always export comprehensive data - re-fetch agent with full details
366
468
  try:
367
- agent = client.agents.get_agent_by_id(agent.id)
469
+ with spinner_context(
470
+ ctx,
471
+ "[bold blue]Fetching complete agent data…[/bold blue]",
472
+ console_override=console,
473
+ ):
474
+ agent = client.agents.get_agent_by_id(agent.id)
368
475
  except Exception as e:
369
476
  handle_rich_output(
370
477
  ctx,
@@ -776,7 +883,7 @@ def _get_language_model_display_name(agent: Any, model: str | None) -> str:
776
883
 
777
884
  def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None:
778
885
  """Handle successful agent creation output."""
779
- handle_json_output(ctx, agent.model_dump())
886
+ handle_json_output(ctx, _prepare_agent_output(agent))
780
887
 
781
888
  lm_display = _get_language_model_display_name(agent, model)
782
889
 
@@ -1080,7 +1187,7 @@ def update(
1080
1187
 
1081
1188
  updated_agent = client.agents.update_agent(agent.id, **update_data)
1082
1189
 
1083
- handle_json_output(ctx, updated_agent.model_dump())
1190
+ handle_json_output(ctx, _prepare_agent_output(updated_agent))
1084
1191
  handle_rich_output(ctx, display_update_success("Agent", updated_agent.name))
1085
1192
  handle_rich_output(ctx, display_agent_run_suggestions(updated_agent))
1086
1193
 
@@ -36,6 +36,7 @@ from glaip_sdk.cli.utils import (
36
36
  output_flags,
37
37
  output_list,
38
38
  output_result,
39
+ spinner_context,
39
40
  )
40
41
  from glaip_sdk.rich_components import AIPPanel
41
42
  from glaip_sdk.utils import format_datetime
@@ -72,7 +73,12 @@ def list_mcps(ctx: Any) -> None:
72
73
  """List all MCPs."""
73
74
  try:
74
75
  client = get_client(ctx)
75
- mcps = client.mcps.list_mcps()
76
+ with spinner_context(
77
+ ctx,
78
+ "[bold blue]Fetching MCPs…[/bold blue]",
79
+ console_override=console,
80
+ ):
81
+ mcps = client.mcps.list_mcps()
76
82
 
77
83
  # Define table columns: (data_key, header, style, width)
78
84
  columns = [
@@ -123,13 +129,18 @@ def create(
123
129
  except json.JSONDecodeError:
124
130
  raise click.ClickException("Invalid JSON in --config")
125
131
 
126
- mcp = client.mcps.create_mcp(
127
- name=name,
128
- type="server", # MCPs are always server type
129
- transport=transport,
130
- description=description,
131
- config=mcp_config,
132
- )
132
+ with spinner_context(
133
+ ctx,
134
+ "[bold blue]Creating MCP…[/bold blue]",
135
+ console_override=console,
136
+ ):
137
+ mcp = client.mcps.create_mcp(
138
+ name=name,
139
+ type="server", # MCPs are always server type
140
+ transport=transport,
141
+ description=description,
142
+ config=mcp_config,
143
+ )
133
144
 
134
145
  # Handle JSON output
135
146
  handle_json_output(ctx, mcp.model_dump())
@@ -183,7 +194,12 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
183
194
 
184
195
  # Always export comprehensive data - re-fetch MCP with full details if needed
185
196
  try:
186
- mcp = client.mcps.get_mcp_by_id(mcp.id)
197
+ with spinner_context(
198
+ ctx,
199
+ "[bold blue]Fetching complete MCP details…[/bold blue]",
200
+ console_override=console,
201
+ ):
202
+ mcp = client.mcps.get_mcp_by_id(mcp.id)
187
203
  except Exception as e:
188
204
  console.print(
189
205
  Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
@@ -192,7 +208,12 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
192
208
  Text("[yellow]⚠️ Proceeding with available data[/yellow]")
193
209
  )
194
210
 
195
- export_resource_to_file(mcp, export_path, detected_format)
211
+ with spinner_context(
212
+ ctx,
213
+ "[bold blue]Exporting MCP configuration…[/bold blue]",
214
+ console_override=console,
215
+ ):
216
+ export_resource_to_file(mcp, export_path, detected_format)
196
217
  console.print(
197
218
  Text(
198
219
  f"[green]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/green]"
@@ -200,7 +221,12 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
200
221
  )
201
222
 
202
223
  # Try to fetch raw API data first to preserve ALL fields
203
- raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
224
+ with spinner_context(
225
+ ctx,
226
+ "[bold blue]Fetching detailed MCP data…[/bold blue]",
227
+ console_override=console,
228
+ ):
229
+ raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
204
230
 
205
231
  if raw_mcp_data:
206
232
  # Use raw API data - this preserves ALL fields
@@ -257,7 +283,12 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
257
283
  mcp = _resolve_mcp(ctx, client, mcp_ref)
258
284
 
259
285
  # Get tools from MCP
260
- tools = client.mcps.get_mcp_tools(mcp.id)
286
+ with spinner_context(
287
+ ctx,
288
+ "[bold blue]Fetching MCP tools…[/bold blue]",
289
+ console_override=console,
290
+ ):
291
+ tools = client.mcps.get_mcp_tools(mcp.id)
261
292
 
262
293
  # Define table columns: (data_key, header, style, width)
263
294
  columns = [
@@ -311,7 +342,12 @@ def connect(ctx: Any, config_file: str) -> None:
311
342
  )
312
343
 
313
344
  # Test connection using config
314
- result = client.mcps.test_mcp_connection_from_config(config)
345
+ with spinner_context(
346
+ ctx,
347
+ "[bold blue]Connecting to MCP…[/bold blue]",
348
+ console_override=console,
349
+ ):
350
+ result = client.mcps.test_mcp_connection_from_config(config)
315
351
 
316
352
  view = get_ctx_value(ctx, "view", "rich")
317
353
  if view == "json":
@@ -366,7 +402,12 @@ def update(
366
402
  raise click.ClickException("No update fields specified")
367
403
 
368
404
  # Update MCP (automatically chooses PUT or PATCH based on provided fields)
369
- updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
405
+ with spinner_context(
406
+ ctx,
407
+ "[bold blue]Updating MCP…[/bold blue]",
408
+ console_override=console,
409
+ ):
410
+ updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
370
411
 
371
412
  handle_json_output(ctx, updated_mcp.model_dump())
372
413
  handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
@@ -395,7 +436,12 @@ def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
395
436
  if not yes and not display_confirmation_prompt("MCP", mcp.name):
396
437
  return
397
438
 
398
- client.mcps.delete_mcp(mcp.id)
439
+ with spinner_context(
440
+ ctx,
441
+ "[bold blue]Deleting MCP…[/bold blue]",
442
+ console_override=console,
443
+ ):
444
+ client.mcps.delete_mcp(mcp.id)
399
445
 
400
446
  handle_json_output(
401
447
  ctx,
@@ -9,7 +9,12 @@ from typing import Any
9
9
  import click
10
10
  from rich.console import Console
11
11
 
12
- from glaip_sdk.cli.utils import get_client, output_flags, output_list
12
+ from glaip_sdk.cli.utils import (
13
+ get_client,
14
+ output_flags,
15
+ output_list,
16
+ spinner_context,
17
+ )
13
18
 
14
19
  console = Console()
15
20
 
@@ -27,7 +32,12 @@ def list_models(ctx: Any) -> None:
27
32
  """List available language models."""
28
33
  try:
29
34
  client = get_client(ctx)
30
- models = client.list_language_models()
35
+ with spinner_context(
36
+ ctx,
37
+ "[bold blue]Fetching language models…[/bold blue]",
38
+ console_override=console,
39
+ ):
40
+ models = client.list_language_models()
31
41
 
32
42
  # Define table columns: (data_key, header, style, width)
33
43
  columns = [
@@ -40,6 +40,7 @@ from glaip_sdk.cli.utils import (
40
40
  output_flags,
41
41
  output_list,
42
42
  output_result,
43
+ spinner_context,
43
44
  )
44
45
  from glaip_sdk.utils import format_datetime
45
46
  from glaip_sdk.utils.import_export import merge_import_with_cli_args
@@ -193,7 +194,12 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
193
194
  """List all tools."""
194
195
  try:
195
196
  client = get_client(ctx)
196
- tools = client.list_tools(tool_type=tool_type)
197
+ with spinner_context(
198
+ ctx,
199
+ "[bold blue]Fetching tools…[/bold blue]",
200
+ console_override=console,
201
+ ):
202
+ tools = client.list_tools(tool_type=tool_type)
197
203
 
198
204
  # Define table columns: (data_key, header, style, width)
199
205
  columns = [
@@ -276,7 +282,12 @@ def create(
276
282
  _validate_creation_parameters(file, import_file)
277
283
 
278
284
  # Create tool from file (either direct file or import file)
279
- tool = _create_tool_from_file(client, file, name, description, tags)
285
+ with spinner_context(
286
+ ctx,
287
+ "[bold blue]Creating tool…[/bold blue]",
288
+ console_override=console,
289
+ ):
290
+ tool = _create_tool_from_file(client, file, name, description, tags)
280
291
 
281
292
  # Handle JSON output
282
293
  handle_json_output(ctx, tool.model_dump())
@@ -333,7 +344,12 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
333
344
 
334
345
  # Always export comprehensive data - re-fetch tool with full details if needed
335
346
  try:
336
- tool = client.get_tool_by_id(tool.id)
347
+ with spinner_context(
348
+ ctx,
349
+ "[bold blue]Fetching complete tool details…[/bold blue]",
350
+ console_override=console,
351
+ ):
352
+ tool = client.get_tool_by_id(tool.id)
337
353
  except Exception as e:
338
354
  console.print(
339
355
  Text(f"[yellow]⚠️ Could not fetch full tool details: {e}[/yellow]")
@@ -342,7 +358,12 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
342
358
  Text("[yellow]⚠️ Proceeding with available data[/yellow]")
343
359
  )
344
360
 
345
- export_resource_to_file(tool, export_path, detected_format)
361
+ with spinner_context(
362
+ ctx,
363
+ "[bold blue]Exporting tool configuration…[/bold blue]",
364
+ console_override=console,
365
+ ):
366
+ export_resource_to_file(tool, export_path, detected_format)
346
367
  console.print(
347
368
  Text(
348
369
  f"[green]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/green]"
@@ -350,7 +371,12 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
350
371
  )
351
372
 
352
373
  # Try to fetch raw API data first to preserve ALL fields
353
- raw_tool_data = fetch_raw_resource_details(client, tool, "tools")
374
+ with spinner_context(
375
+ ctx,
376
+ "[bold blue]Fetching detailed tool data…[/bold blue]",
377
+ console_override=console,
378
+ ):
379
+ raw_tool_data = fetch_raw_resource_details(client, tool, "tools")
354
380
 
355
381
  if raw_tool_data:
356
382
  # Use raw API data - this preserves ALL fields
@@ -418,7 +444,12 @@ def update(
418
444
 
419
445
  # Get tool by ID (no ambiguity handling needed)
420
446
  try:
421
- tool = client.get_tool_by_id(tool_id)
447
+ with spinner_context(
448
+ ctx,
449
+ "[bold blue]Fetching tool…[/bold blue]",
450
+ console_override=console,
451
+ ):
452
+ tool = client.get_tool_by_id(tool_id)
422
453
  except Exception as e:
423
454
  raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}")
424
455
 
@@ -435,9 +466,14 @@ def update(
435
466
  raise click.ClickException(
436
467
  f"File updates are only supported for custom tools. Tool '{tool.name}' is of type '{tool.tool_type}'."
437
468
  )
438
- updated_tool = client.tools.update_tool_via_file(
439
- tool.id, file, framework=tool.framework
440
- )
469
+ with spinner_context(
470
+ ctx,
471
+ "[bold blue]Uploading new tool code…[/bold blue]",
472
+ console_override=console,
473
+ ):
474
+ updated_tool = client.tools.update_tool_via_file(
475
+ tool.id, file, framework=tool.framework
476
+ )
441
477
  handle_rich_output(
442
478
  ctx, Text(f"[green]✓[/green] Tool code updated from {file}")
443
479
  )
@@ -447,7 +483,12 @@ def update(
447
483
  raise click.ClickException(
448
484
  f"Metadata updates are only supported for native tools. Tool '{tool.name}' is of type '{tool.tool_type}'."
449
485
  )
450
- updated_tool = tool.update(**update_data)
486
+ with spinner_context(
487
+ ctx,
488
+ "[bold blue]Updating tool metadata…[/bold blue]",
489
+ console_override=console,
490
+ ):
491
+ updated_tool = tool.update(**update_data)
451
492
  handle_rich_output(ctx, Text("[green]✓[/green] Tool metadata updated"))
452
493
  else:
453
494
  handle_rich_output(ctx, Text("[yellow]No updates specified[/yellow]"))
@@ -475,7 +516,12 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
475
516
 
476
517
  # Get tool by ID (no ambiguity handling needed)
477
518
  try:
478
- tool = client.get_tool_by_id(tool_id)
519
+ with spinner_context(
520
+ ctx,
521
+ "[bold blue]Fetching tool…[/bold blue]",
522
+ console_override=console,
523
+ ):
524
+ tool = client.get_tool_by_id(tool_id)
479
525
  except Exception as e:
480
526
  raise click.ClickException(f"Tool with ID '{tool_id}' not found: {e}")
481
527
 
@@ -483,7 +529,12 @@ def delete(ctx: Any, tool_id: str, yes: bool) -> None:
483
529
  if not yes and not display_confirmation_prompt("Tool", tool.name):
484
530
  return
485
531
 
486
- tool.delete()
532
+ with spinner_context(
533
+ ctx,
534
+ "[bold blue]Deleting tool…[/bold blue]",
535
+ console_override=console,
536
+ ):
537
+ tool.delete()
487
538
 
488
539
  handle_json_output(
489
540
  ctx,
@@ -509,7 +560,12 @@ def script(ctx: Any, tool_id: str) -> None:
509
560
  """Get tool script content."""
510
561
  try:
511
562
  client = get_client(ctx)
512
- script_content = client.get_tool_script(tool_id)
563
+ with spinner_context(
564
+ ctx,
565
+ "[bold blue]Fetching tool script…[/bold blue]",
566
+ console_override=console,
567
+ ):
568
+ script_content = client.get_tool_script(tool_id)
513
569
 
514
570
  if get_ctx_value(ctx, "view") == "json":
515
571
  click.echo(json.dumps({"script": script_content}, indent=2))
glaip_sdk/cli/display.py CHANGED
@@ -247,9 +247,10 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
247
247
 
248
248
  return AIPPanel(
249
249
  f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
250
- f"🚀 Run this agent:\n"
251
- f' [green]aip agents run {agent.id} "Your message here"[/green]\n\n'
252
- f"📋 Or use the agent name:\n"
250
+ f"🚀 Start chatting with [bold]{agent.name}[/bold] right here:\n"
251
+ f" Type your message below and press Enter to run it immediately.\n\n"
252
+ f"📋 Prefer the CLI instead?\n"
253
+ f' [green]aip agents run {agent.id} "Your message here"[/green]\n'
253
254
  f' [green]aip agents run "{agent.name}" "Your message here"[/green]\n\n'
254
255
  f"🔧 Available options:\n"
255
256
  f" [dim]--chat-history[/dim] Include previous conversation\n"
glaip_sdk/cli/main.py CHANGED
@@ -24,11 +24,21 @@ from glaip_sdk.cli.commands.configure import (
24
24
  from glaip_sdk.cli.commands.mcps import mcps_group
25
25
  from glaip_sdk.cli.commands.models import models_group
26
26
  from glaip_sdk.cli.commands.tools import tools_group
27
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
27
+ from glaip_sdk.cli.utils import spinner_context, update_spinner
28
+ from glaip_sdk.config.constants import (
29
+ DEFAULT_AGENT_RUN_TIMEOUT,
30
+ )
28
31
  from glaip_sdk.rich_components import AIPPanel, AIPTable
29
32
 
33
+ # Import SlashSession for potential mocking in tests
34
+ try:
35
+ from glaip_sdk.cli.slash import SlashSession
36
+ except ImportError: # pragma: no cover - optional slash dependencies
37
+ # Slash dependencies might not be available in all environments
38
+ SlashSession = None
39
+
30
40
 
31
- @click.group()
41
+ @click.group(invoke_without_command=True)
32
42
  @click.version_option(version=_SDK_VERSION, prog_name="aip")
33
43
  @click.option("--api-url", envvar="AIP_API_URL", help="AIP API URL")
34
44
  @click.option("--api-key", envvar="AIP_API_KEY", help="AIP API Key")
@@ -72,6 +82,15 @@ def main(
72
82
 
73
83
  ctx.obj["tty"] = not no_tty
74
84
 
85
+ if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
86
+ if _should_launch_slash(ctx) and SlashSession is not None:
87
+ session = SlashSession(ctx)
88
+ session.run()
89
+ ctx.exit()
90
+ else:
91
+ click.echo(ctx.get_help())
92
+ ctx.exit()
93
+
75
94
 
76
95
  # Add command groups
77
96
  main.add_command(agents_group)
@@ -87,6 +106,19 @@ main.add_command(configure_command)
87
106
  # Tip: `--version` is provided by click.version_option above.
88
107
 
89
108
 
109
+ def _should_launch_slash(ctx: click.Context) -> bool:
110
+ """Determine whether to open the command palette automatically."""
111
+
112
+ ctx_obj = ctx.obj or {}
113
+ if not bool(ctx_obj.get("tty", True)):
114
+ return False
115
+
116
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
117
+ return False
118
+
119
+ return True
120
+
121
+
90
122
  @main.command()
91
123
  @click.pass_context
92
124
  def status(ctx: Any) -> None:
@@ -152,9 +184,26 @@ def status(ctx: Any) -> None:
152
184
 
153
185
  # Test connection by listing resources
154
186
  try:
155
- agents = client.list_agents()
156
- tools = client.list_tools()
157
- mcps = client.list_mcps()
187
+ with spinner_context(
188
+ ctx,
189
+ "[bold blue]Checking GL AIP status…[/bold blue]",
190
+ console_override=console,
191
+ spinner_style="cyan",
192
+ ) as status_indicator:
193
+ update_spinner(
194
+ status_indicator, "[bold blue]Fetching agents…[/bold blue]"
195
+ )
196
+ agents = client.list_agents()
197
+
198
+ update_spinner(
199
+ status_indicator, "[bold blue]Fetching tools…[/bold blue]"
200
+ )
201
+ tools = client.list_tools()
202
+
203
+ update_spinner(
204
+ status_indicator, "[bold blue]Fetching MCPs…[/bold blue]"
205
+ )
206
+ mcps = client.list_mcps()
158
207
 
159
208
  # Create status table
160
209
  table = AIPTable(title="🔗 GL AIP Status")