glaip-sdk 0.1.3__py3-none-any.whl → 0.6.10__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 (141) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +9 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1191 -0
  5. glaip_sdk/branding.py +13 -0
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/auth.py +254 -15
  8. glaip_sdk/cli/commands/__init__.py +2 -2
  9. glaip_sdk/cli/commands/accounts.py +746 -0
  10. glaip_sdk/cli/commands/agents.py +213 -73
  11. glaip_sdk/cli/commands/common_config.py +101 -0
  12. glaip_sdk/cli/commands/configure.py +729 -113
  13. glaip_sdk/cli/commands/mcps.py +241 -72
  14. glaip_sdk/cli/commands/models.py +11 -5
  15. glaip_sdk/cli/commands/tools.py +49 -57
  16. glaip_sdk/cli/commands/transcripts.py +755 -0
  17. glaip_sdk/cli/config.py +48 -4
  18. glaip_sdk/cli/constants.py +38 -0
  19. glaip_sdk/cli/context.py +8 -0
  20. glaip_sdk/cli/core/__init__.py +79 -0
  21. glaip_sdk/cli/core/context.py +124 -0
  22. glaip_sdk/cli/core/output.py +846 -0
  23. glaip_sdk/cli/core/prompting.py +649 -0
  24. glaip_sdk/cli/core/rendering.py +187 -0
  25. glaip_sdk/cli/display.py +35 -19
  26. glaip_sdk/cli/hints.py +57 -0
  27. glaip_sdk/cli/io.py +6 -3
  28. glaip_sdk/cli/main.py +228 -119
  29. glaip_sdk/cli/masking.py +21 -33
  30. glaip_sdk/cli/pager.py +9 -10
  31. glaip_sdk/cli/parsers/__init__.py +1 -3
  32. glaip_sdk/cli/slash/__init__.py +0 -9
  33. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  34. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  35. glaip_sdk/cli/slash/agent_session.py +62 -21
  36. glaip_sdk/cli/slash/prompt.py +21 -0
  37. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  38. glaip_sdk/cli/slash/session.py +771 -140
  39. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  40. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  41. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  42. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  43. glaip_sdk/cli/slash/tui/loading.py +58 -0
  44. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  45. glaip_sdk/cli/transcript/__init__.py +12 -52
  46. glaip_sdk/cli/transcript/cache.py +255 -44
  47. glaip_sdk/cli/transcript/capture.py +27 -1
  48. glaip_sdk/cli/transcript/history.py +815 -0
  49. glaip_sdk/cli/transcript/viewer.py +72 -499
  50. glaip_sdk/cli/update_notifier.py +14 -5
  51. glaip_sdk/cli/utils.py +243 -1252
  52. glaip_sdk/cli/validators.py +5 -6
  53. glaip_sdk/client/__init__.py +2 -1
  54. glaip_sdk/client/_agent_payloads.py +45 -9
  55. glaip_sdk/client/agent_runs.py +147 -0
  56. glaip_sdk/client/agents.py +287 -29
  57. glaip_sdk/client/base.py +1 -0
  58. glaip_sdk/client/main.py +19 -10
  59. glaip_sdk/client/mcps.py +122 -12
  60. glaip_sdk/client/run_rendering.py +133 -88
  61. glaip_sdk/client/shared.py +21 -0
  62. glaip_sdk/client/tools.py +155 -10
  63. glaip_sdk/config/constants.py +11 -0
  64. glaip_sdk/mcps/__init__.py +21 -0
  65. glaip_sdk/mcps/base.py +345 -0
  66. glaip_sdk/models/__init__.py +90 -0
  67. glaip_sdk/models/agent.py +47 -0
  68. glaip_sdk/models/agent_runs.py +116 -0
  69. glaip_sdk/models/common.py +42 -0
  70. glaip_sdk/models/mcp.py +33 -0
  71. glaip_sdk/models/tool.py +33 -0
  72. glaip_sdk/payload_schemas/__init__.py +1 -13
  73. glaip_sdk/registry/__init__.py +55 -0
  74. glaip_sdk/registry/agent.py +164 -0
  75. glaip_sdk/registry/base.py +139 -0
  76. glaip_sdk/registry/mcp.py +253 -0
  77. glaip_sdk/registry/tool.py +232 -0
  78. glaip_sdk/rich_components.py +58 -2
  79. glaip_sdk/runner/__init__.py +59 -0
  80. glaip_sdk/runner/base.py +84 -0
  81. glaip_sdk/runner/deps.py +115 -0
  82. glaip_sdk/runner/langgraph.py +706 -0
  83. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  84. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  85. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  86. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  87. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  88. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  89. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  90. glaip_sdk/tools/__init__.py +22 -0
  91. glaip_sdk/tools/base.py +435 -0
  92. glaip_sdk/utils/__init__.py +58 -12
  93. glaip_sdk/utils/a2a/__init__.py +34 -0
  94. glaip_sdk/utils/a2a/event_processor.py +188 -0
  95. glaip_sdk/utils/bundler.py +267 -0
  96. glaip_sdk/utils/client.py +111 -0
  97. glaip_sdk/utils/client_utils.py +39 -7
  98. glaip_sdk/utils/datetime_helpers.py +58 -0
  99. glaip_sdk/utils/discovery.py +78 -0
  100. glaip_sdk/utils/display.py +23 -15
  101. glaip_sdk/utils/export.py +143 -0
  102. glaip_sdk/utils/general.py +0 -33
  103. glaip_sdk/utils/import_export.py +12 -7
  104. glaip_sdk/utils/import_resolver.py +492 -0
  105. glaip_sdk/utils/instructions.py +101 -0
  106. glaip_sdk/utils/rendering/__init__.py +115 -1
  107. glaip_sdk/utils/rendering/formatting.py +5 -30
  108. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  109. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  110. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  111. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  112. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  113. glaip_sdk/utils/rendering/models.py +1 -0
  114. glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
  115. glaip_sdk/utils/rendering/renderer/base.py +217 -1476
  116. glaip_sdk/utils/rendering/renderer/debug.py +26 -20
  117. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  118. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  119. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  120. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  121. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  122. glaip_sdk/utils/rendering/state.py +204 -0
  123. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  124. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
  125. glaip_sdk/utils/rendering/steps/format.py +176 -0
  126. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  127. glaip_sdk/utils/rendering/timing.py +36 -0
  128. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  129. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  130. glaip_sdk/utils/resource_refs.py +25 -13
  131. glaip_sdk/utils/runtime_config.py +425 -0
  132. glaip_sdk/utils/serialization.py +18 -0
  133. glaip_sdk/utils/sync.py +142 -0
  134. glaip_sdk/utils/tool_detection.py +33 -0
  135. glaip_sdk/utils/validation.py +16 -24
  136. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
  137. glaip_sdk-0.6.10.dist-info/RECORD +159 -0
  138. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
  139. glaip_sdk/models.py +0 -240
  140. glaip_sdk-0.1.3.dist-info/RECORD +0 -83
  141. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
  import json
10
10
  import os
11
11
  from collections.abc import Mapping
12
+ from copy import deepcopy
12
13
  from pathlib import Path
13
14
  from typing import Any
14
15
 
@@ -18,6 +19,7 @@ from rich.console import Console
18
19
  from glaip_sdk.branding import (
19
20
  ACCENT_STYLE,
20
21
  ERROR_STYLE,
22
+ HINT_PREFIX_STYLE,
21
23
  INFO,
22
24
  SUCCESS,
23
25
  SUCCESS_STYLE,
@@ -32,7 +34,8 @@ from glaip_sdk.cli.agent_config import (
32
34
  from glaip_sdk.cli.agent_config import (
33
35
  sanitize_agent_config_for_cli as sanitize_agent_config,
34
36
  )
35
- from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
37
+ from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
38
+ from glaip_sdk.cli.context import get_ctx_value, output_flags
36
39
  from glaip_sdk.cli.display import (
37
40
  build_resource_result_data,
38
41
  display_agent_run_suggestions,
@@ -44,9 +47,7 @@ from glaip_sdk.cli.display import (
44
47
  handle_rich_output,
45
48
  print_api_error,
46
49
  )
47
- from glaip_sdk.cli.io import (
48
- export_resource_to_file_with_validation as export_resource_to_file,
49
- )
50
+ from glaip_sdk.cli.hints import in_slash_mode
50
51
  from glaip_sdk.cli.io import (
51
52
  fetch_raw_resource_details,
52
53
  )
@@ -64,9 +65,11 @@ from glaip_sdk.cli.utils import (
64
65
  build_renderer,
65
66
  coerce_to_row,
66
67
  get_client,
68
+ handle_resource_export,
67
69
  output_list,
68
70
  output_result,
69
71
  spinner_context,
72
+ with_client_and_spinner,
70
73
  )
71
74
  from glaip_sdk.cli.validators import (
72
75
  validate_agent_instruction_cli as validate_agent_instruction,
@@ -77,7 +80,7 @@ from glaip_sdk.cli.validators import (
77
80
  from glaip_sdk.cli.validators import (
78
81
  validate_timeout_cli as validate_timeout,
79
82
  )
80
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
83
+ from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS, DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
81
84
  from glaip_sdk.exceptions import AgentTimeoutError
82
85
  from glaip_sdk.icons import ICON_AGENT
83
86
  from glaip_sdk.utils import format_datetime, is_uuid
@@ -91,6 +94,8 @@ console = Console()
91
94
  # Error message constants
92
95
  AGENT_NOT_FOUND_ERROR = "Agent not found"
93
96
 
97
+ # Instruction preview controls
98
+
94
99
 
95
100
  def _safe_agent_attribute(agent: Any, name: str) -> Any:
96
101
  """Return attribute value for ``name`` while filtering Mock sentinels."""
@@ -143,10 +148,7 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
143
148
  "description",
144
149
  "model",
145
150
  "agent_config",
146
- "tools",
147
- "agents",
148
- "mcps",
149
- "timeout",
151
+ *[field for field in AGENT_CONFIG_FIELDS if field not in ("name", "instruction", "model")],
150
152
  "tool_configs",
151
153
  )
152
154
 
@@ -310,12 +312,92 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
310
312
  return result_data
311
313
 
312
314
 
313
- def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
315
+ def _clamp_instruction_preview_limit(limit: int | None) -> int:
316
+ """Normalise preview limit; 0 disables trimming."""
317
+ default = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
318
+ if limit is None: # pragma: no cover
319
+ return default
320
+ try:
321
+ limit_value = int(limit)
322
+ except (TypeError, ValueError): # pragma: no cover - defensive parsing
323
+ return default
324
+
325
+ if limit_value <= 0:
326
+ return 0
327
+
328
+ return limit_value
329
+
330
+
331
+ def _build_instruction_preview(value: Any, limit: int) -> tuple[Any, bool]:
332
+ """Return a trimmed preview for long instruction strings."""
333
+ if not isinstance(value, str) or limit <= 0: # pragma: no cover
334
+ return value, False
335
+
336
+ if len(value) <= limit:
337
+ return value, False
338
+
339
+ trimmed_value = value[:limit].rstrip()
340
+ preview = f"{trimmed_value}\n\n... (preview trimmed)"
341
+ return preview, True
342
+
343
+
344
+ def _prepare_agent_details_payload(
345
+ data: dict[str, Any],
346
+ *,
347
+ instruction_preview_limit: int,
348
+ ) -> tuple[dict[str, Any], bool]:
349
+ """Return payload ready for rendering plus trim indicator."""
350
+ payload = deepcopy(data)
351
+ trimmed = False
352
+ if instruction_preview_limit > 0:
353
+ preview, trimmed = _build_instruction_preview(payload.get("instruction"), instruction_preview_limit)
354
+ if trimmed:
355
+ payload["instruction"] = preview
356
+ return payload, trimmed
357
+
358
+
359
+ def _show_instruction_trim_hint(
360
+ ctx: Any,
361
+ *,
362
+ trimmed: bool,
363
+ preview_limit: int,
364
+ ) -> None:
365
+ """Render hint describing how to expand or collapse the instruction preview."""
366
+ if not trimmed or preview_limit <= 0:
367
+ return
368
+
369
+ view = get_ctx_value(ctx, "view", "rich") if ctx is not None else "rich"
370
+ if view != "rich": # pragma: no cover - non-rich view handling
371
+ return
372
+
373
+ suffix = f"[dim](preview: {preview_limit:,} chars)[/]"
374
+ if in_slash_mode(ctx):
375
+ console.print(
376
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Use '/details' again to toggle between trimmed and full prompts {suffix}"
377
+ )
378
+ return
379
+
380
+ console.print( # pragma: no cover - fallback hint rendering
381
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Run 'aip agents get <agent> --instruction-preview <n>' "
382
+ f"to control prompt preview length {suffix}"
383
+ )
384
+
385
+
386
+ def _display_agent_details(
387
+ ctx: Any,
388
+ client: Any,
389
+ agent: Any,
390
+ *,
391
+ instruction_preview_limit: int | None = None,
392
+ ) -> None:
314
393
  """Display full agent details using raw API data to preserve ALL fields."""
315
394
  if agent is None:
316
395
  handle_rich_output(ctx, markup_text(f"[{ERROR_STYLE}]āŒ No agent provided[/]"))
317
396
  return
318
397
 
398
+ preview_limit = _clamp_instruction_preview_limit(instruction_preview_limit)
399
+ trimmed_instruction = False
400
+
319
401
  # Try to fetch and format raw agent data first
320
402
  with spinner_context(
321
403
  ctx,
@@ -327,9 +409,13 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
327
409
  if formatted_data:
328
410
  # Use raw API data - this preserves ALL fields including account_id
329
411
  panel_title = f"{ICON_AGENT} {formatted_data.get('name', 'Unknown')}"
412
+ payload, trimmed_instruction = _prepare_agent_details_payload(
413
+ formatted_data,
414
+ instruction_preview_limit=preview_limit,
415
+ )
330
416
  output_result(
331
417
  ctx,
332
- formatted_data,
418
+ payload,
333
419
  title=panel_title,
334
420
  )
335
421
  else:
@@ -347,12 +433,22 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
347
433
  result_data = _format_fallback_agent_data(client, agent)
348
434
 
349
435
  # Display using output_result
436
+ payload, trimmed_instruction = _prepare_agent_details_payload(
437
+ result_data,
438
+ instruction_preview_limit=preview_limit,
439
+ )
350
440
  output_result(
351
441
  ctx,
352
- result_data,
442
+ payload,
353
443
  title="Agent Details",
354
444
  )
355
445
 
446
+ _show_instruction_trim_hint(
447
+ ctx,
448
+ trimmed=trimmed_instruction,
449
+ preview_limit=preview_limit,
450
+ )
451
+
356
452
 
357
453
  @click.group(name="agents", no_args_is_help=True)
358
454
  def agents_group() -> None:
@@ -367,26 +463,38 @@ def _resolve_agent(
367
463
  select: int | None = None,
368
464
  interface_preference: str = "fuzzy",
369
465
  ) -> Any | None:
370
- """Resolve agent reference (ID or name) with ambiguity handling.
466
+ """Resolve an agent by ID or name, supporting fuzzy and questionary interfaces.
467
+
468
+ This function provides agent-specific resolution with flexible UI options.
469
+ It wraps resolve_resource_reference with agent-specific configuration, allowing
470
+ users to choose between fuzzy search and traditional questionary selection.
371
471
 
372
472
  Args:
373
- ctx: Click context object for CLI operations.
374
- client: AIP client instance for API operations.
375
- ref: Agent reference (ID or name) to resolve.
376
- select: Pre-selected agent index for non-interactive mode.
377
- interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list.
473
+ ctx: Click context for CLI command execution.
474
+ client: AIP SDK client instance.
475
+ ref: Agent identifier (UUID or name string).
476
+ select: Pre-selected index for non-interactive resolution (1-based).
477
+ interface_preference: UI preference - "fuzzy" for search or "questionary" for list.
378
478
 
379
479
  Returns:
380
- Resolved agent object or None if not found.
480
+ Agent object when found, None when resolution fails.
381
481
  """
482
+ # Configure agent-specific resolution parameters
483
+ resolution_config = {
484
+ "resource_type": "agent",
485
+ "get_by_id": client.agents.get_agent_by_id,
486
+ "find_by_name": client.agents.find_agents,
487
+ "label": "Agent",
488
+ }
489
+ # Use agent-specific resolution with flexible interface preference
382
490
  return resolve_resource_reference(
383
491
  ctx,
384
492
  client,
385
493
  ref,
386
- "agent",
387
- client.agents.get_agent_by_id,
388
- client.agents.find_agents,
389
- "Agent",
494
+ resolution_config["resource_type"],
495
+ resolution_config["get_by_id"],
496
+ resolution_config["find_by_name"],
497
+ resolution_config["label"],
390
498
  select=select,
391
499
  interface_preference=interface_preference,
392
500
  )
@@ -416,19 +524,20 @@ def list_agents(
416
524
  ) -> None:
417
525
  """List agents with optional filtering."""
418
526
  try:
419
- client = get_client(ctx)
420
- with spinner_context(
527
+ with with_client_and_spinner(
421
528
  ctx,
422
529
  "[bold blue]Fetching agents…[/bold blue]",
423
530
  console_override=console,
424
- ):
425
- agents = client.agents.list_agents(
426
- agent_type=agent_type,
427
- framework=framework,
428
- name=name,
429
- version=version,
430
- sync_langflow_agents=sync_langflow,
431
- )
531
+ ) as client:
532
+ # Query agents with specified filters
533
+ filter_params = {
534
+ "agent_type": agent_type,
535
+ "framework": framework,
536
+ "name": name,
537
+ "version": version,
538
+ "sync_langflow_agents": sync_langflow,
539
+ }
540
+ agents = client.agents.list_agents(**filter_params)
432
541
 
433
542
  # Define table columns: (data_key, header, style, width)
434
543
  columns = [
@@ -441,6 +550,14 @@ def list_agents(
441
550
 
442
551
  # Transform function for safe attribute access
443
552
  def transform_agent(agent: Any) -> dict[str, Any]:
553
+ """Transform an agent object to a display row dictionary.
554
+
555
+ Args:
556
+ agent: Agent object to transform.
557
+
558
+ Returns:
559
+ Dictionary with id, name, type, framework, and version fields.
560
+ """
444
561
  row = coerce_to_row(agent, ["id", "name", "type", "framework", "version"])
445
562
  # Ensure id is always a string
446
563
  row["id"] = str(row["id"])
@@ -459,7 +576,10 @@ def list_agents(
459
576
  and len(agents) > 0
460
577
  )
461
578
 
579
+ # Track picker attempt so the fallback table doesn't re-open the palette
580
+ picker_attempted = False
462
581
  if interactive_enabled:
582
+ picker_attempted = True
463
583
  picked_agent = _fuzzy_pick_for_resources(agents, "agent", "")
464
584
  if picked_agent:
465
585
  _display_agent_details(ctx, client, picked_agent)
@@ -474,7 +594,12 @@ def list_agents(
474
594
  f"{ICON_AGENT} Available Agents",
475
595
  columns,
476
596
  transform_agent,
477
- skip_picker=simple or any(param is not None for param in (agent_type, framework, name, version)),
597
+ skip_picker=(
598
+ not interactive_enabled
599
+ or picker_attempted
600
+ or simple
601
+ or any(param is not None for param in (agent_type, framework, name, version))
602
+ ),
478
603
  use_pager=False,
479
604
  )
480
605
 
@@ -490,61 +615,64 @@ def list_agents(
490
615
  type=click.Path(dir_okay=False, writable=True),
491
616
  help="Export complete agent configuration to file (format auto-detected from .json/.yaml extension)",
492
617
  )
618
+ @click.option(
619
+ "--instruction-preview",
620
+ type=int,
621
+ default=0,
622
+ show_default=True,
623
+ help="Instruction preview length when printing instructions (0 shows full prompt).",
624
+ )
493
625
  @output_flags()
494
626
  @click.pass_context
495
- def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> None:
496
- """Get agent details.
627
+ def get(
628
+ ctx: Any,
629
+ agent_ref: str,
630
+ select: int | None,
631
+ export: str | None,
632
+ instruction_preview: int,
633
+ ) -> None:
634
+ r"""Get agent details.
497
635
 
636
+ \b
498
637
  Examples:
499
638
  aip agents get my-agent
500
639
  aip agents get my-agent --export agent.json # Exports complete configuration as JSON
501
640
  aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
502
641
  """
503
642
  try:
504
- client = get_client(ctx)
643
+ # Initialize API client for agent retrieval
644
+ api_client = get_client(ctx)
645
+
646
+ # Resolve agent reference using questionary interface for better UX
647
+ agent = _resolve_agent(ctx, api_client, agent_ref, select, interface_preference="questionary")
505
648
 
506
- # Resolve agent with ambiguity handling - use questionary interface for traditional UX
507
- agent = _resolve_agent(ctx, client, agent_ref, select, interface_preference="questionary")
649
+ if not agent:
650
+ raise click.ClickException(f"Agent '{agent_ref}' not found")
508
651
 
509
- # Handle export option
652
+ # Handle export option if requested
510
653
  if export:
511
- export_path = Path(export)
512
- # Auto-detect format from file extension
513
- detected_format = detect_export_format(export_path)
514
-
515
- # Always export comprehensive data - re-fetch agent with full details
516
- try:
517
- with spinner_context(
518
- ctx,
519
- "[bold blue]Fetching complete agent data…[/bold blue]",
520
- console_override=console,
521
- ):
522
- agent = client.agents.get_agent_by_id(agent.id)
523
- except Exception as e:
524
- handle_rich_output(
525
- ctx,
526
- markup_text(f"[{WARNING_STYLE}]āš ļø Could not fetch full agent details: {e}[/]"),
527
- )
528
- handle_rich_output(
529
- ctx,
530
- markup_text(f"[{WARNING_STYLE}]āš ļø Proceeding with available data[/]"),
531
- )
532
-
533
- export_resource_to_file(agent, export_path, detected_format)
534
- handle_rich_output(
654
+ handle_resource_export(
535
655
  ctx,
536
- markup_text(
537
- f"[{SUCCESS_STYLE}]āœ… Complete agent configuration exported to: {export_path} "
538
- f"(format: {detected_format})[/]"
539
- ),
656
+ agent,
657
+ Path(export),
658
+ resource_type="agent",
659
+ get_by_id_func=api_client.agents.get_agent_by_id,
660
+ console_override=console,
540
661
  )
541
662
 
542
663
  # Display full agent details using the standardized helper
543
- _display_agent_details(ctx, client, agent)
664
+ _display_agent_details(
665
+ ctx,
666
+ api_client,
667
+ agent,
668
+ instruction_preview_limit=instruction_preview,
669
+ )
544
670
 
545
671
  # Show run suggestions via centralized display helper
546
672
  handle_rich_output(ctx, display_agent_run_suggestions(agent))
547
673
 
674
+ except click.ClickException:
675
+ raise
548
676
  except Exception as e:
549
677
  raise click.ClickException(str(e)) from e
550
678
 
@@ -706,10 +834,11 @@ def run(
706
834
  files: tuple[str, ...] | None,
707
835
  verbose: bool,
708
836
  ) -> None:
709
- """Run an agent with input text.
837
+ r"""Run an agent with input text.
710
838
 
711
839
  Usage: aip agents run <agent_ref> <input_text> [OPTIONS]
712
840
 
841
+ \b
713
842
  Examples:
714
843
  aip agents run my-agent "Hello world"
715
844
  aip agents run agent-123 "Process this data" --timeout 600
@@ -778,11 +907,13 @@ def run(
778
907
 
779
908
 
780
909
  def _running_in_slash_mode(ctx: Any) -> bool:
910
+ """Return True if the command is executing inside the slash session."""
781
911
  ctx_obj = getattr(ctx, "obj", None)
782
912
  return isinstance(ctx_obj, dict) and bool(ctx_obj.get("_slash_session"))
783
913
 
784
914
 
785
915
  def _emit_verbose_guidance(ctx: Any) -> None:
916
+ """Explain the modern alternative to the deprecated --verbose flag."""
786
917
  if _running_in_slash_mode(ctx):
787
918
  message = (
788
919
  "[dim]Tip:[/] Verbose streaming has been retired in the command palette. Run the agent normally and open "
@@ -1037,8 +1168,9 @@ def create(
1037
1168
  timeout: float | None,
1038
1169
  import_file: str | None,
1039
1170
  ) -> None:
1040
- """Create a new agent.
1171
+ r"""Create a new agent.
1041
1172
 
1173
+ \b
1042
1174
  Examples:
1043
1175
  aip agents create --name "My Agent" --instruction "You are a helpful assistant"
1044
1176
  aip agents create --import agent.json
@@ -1232,8 +1364,9 @@ def update(
1232
1364
  timeout: float | None,
1233
1365
  import_file: str | None,
1234
1366
  ) -> None:
1235
- """Update an existing agent.
1367
+ r"""Update an existing agent.
1236
1368
 
1369
+ \b
1237
1370
  Examples:
1238
1371
  aip agents update my-agent --instruction "New instruction"
1239
1372
  aip agents update my-agent --import agent.json
@@ -1257,6 +1390,12 @@ def update(
1257
1390
 
1258
1391
  if merged_data:
1259
1392
  _handle_update_import_config(import_file, merged_data, update_data)
1393
+ # Ensure instruction from import file is included if not already set via CLI
1394
+ # This handles the case where instruction is None in CLI args but exists in import file
1395
+ if import_file and (instruction is None or "instruction" not in update_data):
1396
+ import_instruction = merged_data.get("instruction")
1397
+ if import_instruction is not None:
1398
+ update_data["instruction"] = import_instruction
1260
1399
 
1261
1400
  if not update_data:
1262
1401
  raise click.ClickException("No update fields specified")
@@ -1327,7 +1466,7 @@ def delete(ctx: Any, agent_id: str, yes: bool) -> None:
1327
1466
  @output_flags()
1328
1467
  @click.pass_context
1329
1468
  def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1330
- """Sync agents with LangFlow server flows.
1469
+ r"""Sync agents with LangFlow server flows.
1331
1470
 
1332
1471
  This command fetches all flows from the configured LangFlow server and
1333
1472
  creates/updates corresponding agents in the platform.
@@ -1336,6 +1475,7 @@ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1336
1475
  - Command options (--base-url, --api-key)
1337
1476
  - Environment variables (LANGFLOW_BASE_URL, LANGFLOW_API_KEY)
1338
1477
 
1478
+ \b
1339
1479
  Examples:
1340
1480
  aip agents sync-langflow
1341
1481
  aip agents sync-langflow --base-url https://my-langflow.com --api-key my-key
@@ -0,0 +1,101 @@
1
+ """Shared helpers for configuration/account flows."""
2
+
3
+ import click
4
+ import logging
5
+ from rich.console import Console
6
+ from rich.text import Text
7
+
8
+ from glaip_sdk import Client
9
+ from glaip_sdk.branding import PRIMARY, SUCCESS_STYLE, WARNING_STYLE, AIPBranding
10
+ from glaip_sdk.cli.utils import sdk_version
11
+
12
+
13
+ def render_branding_header(console: Console, rule_text: str) -> None:
14
+ """Render the standard CLI branding header with a custom rule text."""
15
+ branding = AIPBranding.create_from_sdk(sdk_version=sdk_version(), package_name="glaip-sdk")
16
+ heading = "[bold]>_ GDP Labs AI Agents Package (AIP CLI)[/bold]"
17
+ console.print(heading)
18
+ console.print()
19
+ console.print(branding.get_welcome_banner())
20
+ console.rule(rule_text, style=PRIMARY)
21
+
22
+
23
+ def check_connection(
24
+ api_url: str,
25
+ api_key: str,
26
+ console: Console,
27
+ *,
28
+ abort_on_error: bool = False,
29
+ extra_hint: str | None = None,
30
+ ) -> bool:
31
+ """Test connectivity and report results.
32
+
33
+ Returns True on success, False on handled failures. Raises click.Abort when
34
+ abort_on_error is True and a fatal error occurs.
35
+ """
36
+ console.print("\nšŸ”Œ Testing connection...")
37
+ client: Client | None = None
38
+ try:
39
+ # Import lazily so test patches targeting glaip_sdk.Client are honored
40
+ from importlib import import_module # noqa: PLC0415
41
+
42
+ client_module = import_module("glaip_sdk")
43
+ client = client_module.Client(api_url=api_url, api_key=api_key)
44
+ try:
45
+ agents = client.list_agents()
46
+ console.print(Text(f"āœ… Connection successful! Found {len(agents)} agents", style=SUCCESS_STYLE))
47
+ return True
48
+ except Exception as exc: # pragma: no cover - API failures depend on network
49
+ console.print(Text(f"āš ļø Connection established but API call failed: {exc}", style=WARNING_STYLE))
50
+ console.print(" You may need to check your API permissions or network access")
51
+ if extra_hint:
52
+ console.print(extra_hint)
53
+ if abort_on_error:
54
+ raise click.Abort() from exc
55
+ return False
56
+ except Exception as exc:
57
+ console.print(Text(f"āŒ Connection failed: {exc}"))
58
+ console.print(" Please check your API URL and key")
59
+ if extra_hint:
60
+ console.print(extra_hint)
61
+ if abort_on_error:
62
+ raise click.Abort() from exc
63
+ return False
64
+ finally:
65
+ if client is not None:
66
+ client.close()
67
+
68
+
69
+ def check_connection_with_reason(
70
+ api_url: str,
71
+ api_key: str,
72
+ *,
73
+ abort_on_error: bool = False,
74
+ ) -> tuple[bool, str]:
75
+ """Test connectivity and return structured reason."""
76
+ client: Client | None = None
77
+ try:
78
+ # Import lazily so test patches targeting glaip_sdk.Client are honored
79
+ from importlib import import_module # noqa: PLC0415
80
+
81
+ client_module = import_module("glaip_sdk")
82
+ client = client_module.Client(api_url=api_url, api_key=api_key)
83
+ try:
84
+ client.list_agents()
85
+ return True, ""
86
+ except Exception as exc: # pragma: no cover - API failures depend on network
87
+ if abort_on_error:
88
+ raise click.Abort() from exc
89
+ return False, f"api_failed: {exc}"
90
+ except Exception as exc:
91
+ # Log unexpected exceptions in debug while keeping CLI-friendly messaging
92
+ logging.getLogger(__name__).debug("Unexpected connection error", exc_info=exc)
93
+ if abort_on_error:
94
+ raise click.Abort() from exc
95
+ return False, f"connection_failed: {exc}"
96
+ finally:
97
+ if client is not None:
98
+ try:
99
+ client.close()
100
+ except Exception:
101
+ pass