glaip-sdk 0.0.15__py3-none-any.whl → 0.0.17__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 (43) hide show
  1. glaip_sdk/__init__.py +1 -1
  2. glaip_sdk/branding.py +28 -2
  3. glaip_sdk/cli/commands/agents.py +36 -27
  4. glaip_sdk/cli/commands/configure.py +46 -52
  5. glaip_sdk/cli/commands/mcps.py +19 -22
  6. glaip_sdk/cli/commands/tools.py +19 -13
  7. glaip_sdk/cli/config.py +42 -0
  8. glaip_sdk/cli/display.py +97 -30
  9. glaip_sdk/cli/main.py +141 -124
  10. glaip_sdk/cli/mcp_validators.py +2 -2
  11. glaip_sdk/cli/pager.py +3 -2
  12. glaip_sdk/cli/parsers/json_input.py +2 -2
  13. glaip_sdk/cli/resolution.py +12 -10
  14. glaip_sdk/cli/rich_helpers.py +29 -0
  15. glaip_sdk/cli/slash/agent_session.py +7 -0
  16. glaip_sdk/cli/slash/prompt.py +21 -2
  17. glaip_sdk/cli/slash/session.py +15 -21
  18. glaip_sdk/cli/update_notifier.py +8 -2
  19. glaip_sdk/cli/utils.py +115 -58
  20. glaip_sdk/client/_agent_payloads.py +504 -0
  21. glaip_sdk/client/agents.py +633 -559
  22. glaip_sdk/client/base.py +92 -20
  23. glaip_sdk/client/main.py +14 -0
  24. glaip_sdk/client/run_rendering.py +275 -0
  25. glaip_sdk/config/constants.py +4 -1
  26. glaip_sdk/exceptions.py +15 -0
  27. glaip_sdk/models.py +5 -0
  28. glaip_sdk/payload_schemas/__init__.py +19 -0
  29. glaip_sdk/payload_schemas/agent.py +87 -0
  30. glaip_sdk/rich_components.py +12 -0
  31. glaip_sdk/utils/client_utils.py +12 -0
  32. glaip_sdk/utils/import_export.py +2 -2
  33. glaip_sdk/utils/rendering/formatting.py +5 -0
  34. glaip_sdk/utils/rendering/models.py +22 -0
  35. glaip_sdk/utils/rendering/renderer/base.py +9 -1
  36. glaip_sdk/utils/rendering/renderer/panels.py +0 -1
  37. glaip_sdk/utils/rendering/steps.py +59 -0
  38. glaip_sdk/utils/serialization.py +24 -3
  39. {glaip_sdk-0.0.15.dist-info → glaip_sdk-0.0.17.dist-info}/METADATA +2 -2
  40. glaip_sdk-0.0.17.dist-info/RECORD +73 -0
  41. glaip_sdk-0.0.15.dist-info/RECORD +0 -67
  42. {glaip_sdk-0.0.15.dist-info → glaip_sdk-0.0.17.dist-info}/WHEEL +0 -0
  43. {glaip_sdk-0.0.15.dist-info → glaip_sdk-0.0.17.dist-info}/entry_points.txt +0 -0
glaip_sdk/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- """GL AIP SDK - Python SDK for GDP Labs AI Agent Package.
1
+ """GL AIP - Python SDK for GDP Labs AI Agent Package.
2
2
 
3
3
  Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
glaip_sdk/branding.py CHANGED
@@ -42,7 +42,7 @@ LABEL = "bold"
42
42
 
43
43
 
44
44
  class AIPBranding:
45
- """GL AIP SDK branding utilities with ASCII banner and version display."""
45
+ """GL AIP branding utilities with ASCII banner and version display."""
46
46
 
47
47
  # GL AIP ASCII art - Modern block style with enhanced visibility
48
48
  AIP_LOGO = r"""
@@ -60,7 +60,8 @@ GDP Labs AI Agents Package
60
60
  version: str | None = None,
61
61
  package_name: str | None = None,
62
62
  ) -> None:
63
- """
63
+ """Initialize AIPBranding instance.
64
+
64
65
  Args:
65
66
  version: Explicit SDK version (overrides auto-detection).
66
67
  package_name: If set, attempt to read version from installed package.
@@ -105,6 +106,11 @@ GDP Labs AI Agents Package
105
106
  return banner
106
107
 
107
108
  def get_version_info(self) -> dict:
109
+ """Get comprehensive version information for the SDK.
110
+
111
+ Returns:
112
+ Dictionary containing version, Python version, platform, and architecture info
113
+ """
108
114
  return {
109
115
  "version": self.version,
110
116
  "python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
@@ -113,6 +119,11 @@ GDP Labs AI Agents Package
113
119
  }
114
120
 
115
121
  def display_welcome_panel(self, title: str = "Welcome to AIP") -> None:
122
+ """Display a welcome panel with branding.
123
+
124
+ Args:
125
+ title: Custom title for the welcome panel
126
+ """
116
127
  banner = self.get_welcome_banner()
117
128
  panel = AIPPanel(
118
129
  banner,
@@ -123,6 +134,7 @@ GDP Labs AI Agents Package
123
134
  self.console.print(panel)
124
135
 
125
136
  def display_version_panel(self) -> None:
137
+ """Display a panel with comprehensive version information."""
126
138
  v = self.get_version_info()
127
139
  version_text = (
128
140
  f"[{TITLE_STYLE}]AIP SDK Version Information[/{TITLE_STYLE}]\n\n"
@@ -140,6 +152,11 @@ GDP Labs AI Agents Package
140
152
  self.console.print(panel)
141
153
 
142
154
  def display_status_banner(self, status: str = "ready") -> None:
155
+ """Display a status banner for the current state.
156
+
157
+ Args:
158
+ status: Current status to display
159
+ """
143
160
  # Keep it simple (no emoji); easy to parse in logs/CI
144
161
  banner = f"[{LABEL}]AIP[/{LABEL}] - {status.title()}"
145
162
  self.console.print(banner)
@@ -148,4 +165,13 @@ GDP Labs AI Agents Package
148
165
  def create_from_sdk(
149
166
  cls, sdk_version: str | None = None, package_name: str | None = None
150
167
  ) -> AIPBranding:
168
+ """Create AIPBranding instance from SDK package information.
169
+
170
+ Args:
171
+ sdk_version: Explicit SDK version override
172
+ package_name: Package name to read version from
173
+
174
+ Returns:
175
+ AIPBranding instance
176
+ """
151
177
  return cls(version=sdk_version, package_name=package_name)
@@ -12,7 +12,6 @@ from typing import Any
12
12
 
13
13
  import click
14
14
  from rich.console import Console
15
- from rich.text import Text
16
15
 
17
16
  from glaip_sdk.cli.agent_config import (
18
17
  merge_agent_config_with_cli_args as merge_import_with_cli_args,
@@ -45,6 +44,7 @@ from glaip_sdk.cli.io import (
45
44
  load_resource_from_file_with_validation as load_resource_from_file,
46
45
  )
47
46
  from glaip_sdk.cli.resolution import resolve_resource_reference
47
+ from glaip_sdk.cli.rich_helpers import markup_text, print_markup
48
48
  from glaip_sdk.cli.utils import (
49
49
  _fuzzy_pick_for_resources,
50
50
  build_renderer,
@@ -78,7 +78,6 @@ AGENT_NOT_FOUND_ERROR = "Agent not found"
78
78
 
79
79
  def _safe_agent_attribute(agent: Any, name: str) -> Any:
80
80
  """Return attribute value for ``name`` while filtering Mock sentinels."""
81
-
82
81
  try:
83
82
  value = getattr(agent, name)
84
83
  except Exception:
@@ -91,7 +90,6 @@ def _safe_agent_attribute(agent: Any, name: str) -> Any:
91
90
 
92
91
  def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
93
92
  """Convert a mapping-like candidate to a plain dict when possible."""
94
-
95
93
  if candidate is None:
96
94
  return None
97
95
  if isinstance(candidate, Mapping):
@@ -101,7 +99,6 @@ def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
101
99
 
102
100
  def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
103
101
  """Attempt to call the named method and coerce its output to a dict."""
104
-
105
102
  method = getattr(agent, method_name, None)
106
103
  if not callable(method):
107
104
  return None
@@ -114,7 +111,6 @@ def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
114
111
 
115
112
  def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
116
113
  """Try standard serialisation helpers to produce a mapping."""
117
-
118
114
  for attr in ("model_dump", "dict", "to_dict"):
119
115
  mapping = _call_agent_method(agent, attr)
120
116
  if mapping is not None:
@@ -124,7 +120,6 @@ def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
124
120
 
125
121
  def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
126
122
  """Construct a minimal mapping from well-known agent attributes."""
127
-
128
123
  fallback_fields = (
129
124
  "id",
130
125
  "name",
@@ -136,6 +131,7 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
136
131
  "agents",
137
132
  "mcps",
138
133
  "timeout",
134
+ "tool_configs",
139
135
  )
140
136
 
141
137
  fallback: dict[str, Any] = {}
@@ -149,7 +145,6 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
149
145
 
150
146
  def _prepare_agent_output(agent: Any) -> dict[str, Any]:
151
147
  """Build a JSON-serialisable mapping for CLI output."""
152
-
153
148
  method_mapping = _coerce_agent_via_methods(agent)
154
149
  if method_mapping is not None:
155
150
  return method_mapping
@@ -256,11 +251,11 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
256
251
  "metadata",
257
252
  "language_model_id",
258
253
  "agent_config",
259
- "tool_configs",
260
254
  "tools",
261
255
  "agents",
262
256
  "mcps",
263
257
  "a2a_profile",
258
+ "tool_configs",
264
259
  ]
265
260
 
266
261
  result_data = build_resource_result_data(full_agent, fields)
@@ -280,7 +275,7 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
280
275
  def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
281
276
  """Display full agent details using raw API data to preserve ALL fields."""
282
277
  if agent is None:
283
- handle_rich_output(ctx, Text("[red]❌ No agent provided[/red]"))
278
+ handle_rich_output(ctx, markup_text("[red]❌ No agent provided[/red]"))
284
279
  return
285
280
 
286
281
  # Try to fetch and format raw agent data first
@@ -297,13 +292,13 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
297
292
  output_result(
298
293
  ctx,
299
294
  formatted_data,
300
- title="Agent Details",
301
- panel_title=panel_title,
295
+ title=panel_title,
302
296
  )
303
297
  else:
304
298
  # Fall back to Pydantic model data if raw fetch fails
305
299
  handle_rich_output(
306
- ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
300
+ ctx,
301
+ markup_text("[yellow]Falling back to Pydantic model data[/yellow]"),
307
302
  )
308
303
 
309
304
  with spinner_context(
@@ -318,7 +313,6 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
318
313
  ctx,
319
314
  result_data,
320
315
  title="Agent Details",
321
- panel_title=f"🤖 {result_data.get('name', 'Unknown')}",
322
316
  )
323
317
 
324
318
 
@@ -338,7 +332,14 @@ def _resolve_agent(
338
332
  """Resolve agent reference (ID or name) with ambiguity handling.
339
333
 
340
334
  Args:
341
- interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
335
+ ctx: Click context object for CLI operations.
336
+ client: AIP client instance for API operations.
337
+ ref: Agent reference (ID or name) to resolve.
338
+ select: Pre-selected agent index for non-interactive mode.
339
+ interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list.
340
+
341
+ Returns:
342
+ Resolved agent object or None if not found.
342
343
  """
343
344
  return resolve_resource_reference(
344
345
  ctx,
@@ -495,18 +496,19 @@ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> Non
495
496
  except Exception as e:
496
497
  handle_rich_output(
497
498
  ctx,
498
- Text(
499
+ markup_text(
499
500
  f"[yellow]⚠️ Could not fetch full agent details: {e}[/yellow]"
500
501
  ),
501
502
  )
502
503
  handle_rich_output(
503
- ctx, Text("[yellow]⚠️ Proceeding with available data[/yellow]")
504
+ ctx,
505
+ markup_text("[yellow]⚠️ Proceeding with available data[/yellow]"),
504
506
  )
505
507
 
506
508
  export_resource_to_file(agent, export_path, detected_format)
507
509
  handle_rich_output(
508
510
  ctx,
509
- Text(
511
+ markup_text(
510
512
  f"[green]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/green]"
511
513
  ),
512
514
  )
@@ -619,7 +621,7 @@ def _save_run_transcript(save: str | None, result: Any, working_console: Any) ->
619
621
 
620
622
  with open(save, "w", encoding="utf-8") as f:
621
623
  f.write(content)
622
- console.print(Text(f"[green]Full debug output saved to: {save}[/green]"))
624
+ print_markup(f"[green]Full debug output saved to: {save}[/green]", console=console)
623
625
 
624
626
 
625
627
  @agents_group.command()
@@ -878,7 +880,6 @@ def _add_import_file_attributes(
878
880
  "type",
879
881
  "framework",
880
882
  "version",
881
- "tool_configs",
882
883
  "mcps",
883
884
  "a2a_profile",
884
885
  }
@@ -1050,6 +1051,7 @@ def _handle_update_import_file(
1050
1051
  instruction: str | None,
1051
1052
  tools: tuple[str, ...] | None,
1052
1053
  agents: tuple[str, ...] | None,
1054
+ mcps: tuple[str, ...] | None,
1053
1055
  timeout: float | None,
1054
1056
  ) -> tuple[
1055
1057
  Any | None,
@@ -1057,11 +1059,12 @@ def _handle_update_import_file(
1057
1059
  str | None,
1058
1060
  tuple[str, ...] | None,
1059
1061
  tuple[str, ...] | None,
1062
+ tuple[str, ...] | None,
1060
1063
  float | None,
1061
1064
  ]:
1062
1065
  """Handle import file processing for agent update."""
1063
1066
  if not import_file:
1064
- return None, name, instruction, tools, agents, timeout
1067
+ return None, name, instruction, tools, agents, mcps, timeout
1065
1068
 
1066
1069
  import_data = load_resource_from_file(Path(import_file), "agent")
1067
1070
  import_data = convert_export_to_import_format(import_data)
@@ -1072,6 +1075,7 @@ def _handle_update_import_file(
1072
1075
  "instruction": instruction,
1073
1076
  "tools": tools or (),
1074
1077
  "agents": agents or (),
1078
+ "mcps": mcps or (),
1075
1079
  "timeout": timeout,
1076
1080
  }
1077
1081
 
@@ -1083,6 +1087,7 @@ def _handle_update_import_file(
1083
1087
  merged_data.get("instruction"),
1084
1088
  tuple(merged_data.get("tools", ())),
1085
1089
  tuple(merged_data.get("agents", ())),
1090
+ tuple(merged_data.get("mcps", ())),
1086
1091
  coerce_timeout(merged_data.get("timeout")),
1087
1092
  )
1088
1093
 
@@ -1092,6 +1097,7 @@ def _build_update_data(
1092
1097
  instruction: str | None,
1093
1098
  tools: tuple[str, ...] | None,
1094
1099
  agents: tuple[str, ...] | None,
1100
+ mcps: tuple[str, ...] | None,
1095
1101
  timeout: float | None,
1096
1102
  ) -> dict[str, Any]:
1097
1103
  """Build the update data dictionary from provided parameters."""
@@ -1104,6 +1110,8 @@ def _build_update_data(
1104
1110
  update_data["tools"] = list(tools)
1105
1111
  if agents:
1106
1112
  update_data["agents"] = list(agents)
1113
+ if mcps:
1114
+ update_data["mcps"] = list(mcps)
1107
1115
  if timeout is not None:
1108
1116
  update_data["timeout"] = timeout
1109
1117
  return update_data
@@ -1141,8 +1149,6 @@ def _handle_update_import_config(
1141
1149
  "type",
1142
1150
  "framework",
1143
1151
  "version",
1144
- "tool_configs",
1145
- "mcps",
1146
1152
  "a2a_profile",
1147
1153
  }
1148
1154
  for key, value in merged_data.items():
@@ -1156,6 +1162,7 @@ def _handle_update_import_config(
1156
1162
  @click.option("--instruction", help="New instruction")
1157
1163
  @click.option("--tools", multiple=True, help="New tool names or IDs")
1158
1164
  @click.option("--agents", multiple=True, help="New sub-agent names")
1165
+ @click.option("--mcps", multiple=True, help="New MCP names or IDs")
1159
1166
  @click.option("--timeout", type=int, help="New timeout value")
1160
1167
  @click.option(
1161
1168
  "--import",
@@ -1172,6 +1179,7 @@ def update(
1172
1179
  instruction: str | None,
1173
1180
  tools: tuple[str, ...] | None,
1174
1181
  agents: tuple[str, ...] | None,
1182
+ mcps: tuple[str, ...] | None,
1175
1183
  timeout: float | None,
1176
1184
  import_file: str | None,
1177
1185
  ) -> None:
@@ -1192,12 +1200,15 @@ def update(
1192
1200
  instruction,
1193
1201
  tools,
1194
1202
  agents,
1203
+ mcps,
1195
1204
  timeout,
1196
1205
  ) = _handle_update_import_file(
1197
- import_file, name, instruction, tools, agents, timeout
1206
+ import_file, name, instruction, tools, agents, mcps, timeout
1198
1207
  )
1199
1208
 
1200
- update_data = _build_update_data(name, instruction, tools, agents, timeout)
1209
+ update_data = _build_update_data(
1210
+ name, instruction, tools, agents, mcps, timeout
1211
+ )
1201
1212
 
1202
1213
  if merged_data:
1203
1214
  _handle_update_import_config(import_file, merged_data, update_data)
@@ -1297,8 +1308,6 @@ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1297
1308
 
1298
1309
  # Show success message for non-JSON output
1299
1310
  if get_ctx_value(ctx, "view") != "json":
1300
- from rich.text import Text
1301
-
1302
1311
  # Extract some useful info from the result
1303
1312
  success_count = result.get("data", {}).get("created_count", 0) + result.get(
1304
1313
  "data", {}
@@ -1307,7 +1316,7 @@ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1307
1316
 
1308
1317
  handle_rich_output(
1309
1318
  ctx,
1310
- Text(
1319
+ markup_text(
1311
1320
  f"[green]✅ Successfully synced {success_count} LangFlow agents ({total_count} total processed)[/green]"
1312
1321
  ),
1313
1322
  )
@@ -5,51 +5,21 @@ Authors:
5
5
  """
6
6
 
7
7
  import getpass
8
- import os
9
- from pathlib import Path
10
- from typing import Any
11
8
 
12
9
  import click
13
- import yaml
14
10
  from rich.console import Console
15
11
  from rich.text import Text
16
12
 
17
13
  from glaip_sdk import Client
18
14
  from glaip_sdk._version import __version__ as _SDK_VERSION
19
15
  from glaip_sdk.branding import AIPBranding
16
+ from glaip_sdk.cli.config import CONFIG_FILE, load_config, save_config
17
+ from glaip_sdk.cli.rich_helpers import markup_text
18
+ from glaip_sdk.cli.utils import command_hint
20
19
  from glaip_sdk.rich_components import AIPTable
21
20
 
22
21
  console = Console()
23
22
 
24
- CONFIG_DIR = Path.home() / ".aip"
25
- CONFIG_FILE = CONFIG_DIR / "config.yaml"
26
-
27
-
28
- def load_config() -> dict[str, Any]:
29
- """Load configuration from file."""
30
- if not CONFIG_FILE.exists():
31
- return {}
32
-
33
- try:
34
- with open(CONFIG_FILE) as f:
35
- return yaml.safe_load(f) or {}
36
- except yaml.YAMLError:
37
- return {}
38
-
39
-
40
- def save_config(config: dict[str, Any]) -> None:
41
- """Save configuration to file."""
42
- CONFIG_DIR.mkdir(exist_ok=True)
43
-
44
- with open(CONFIG_FILE, "w") as f:
45
- yaml.dump(config, f, default_flow_style=False)
46
-
47
- # Set secure file permissions
48
- try:
49
- os.chmod(CONFIG_FILE, 0o600)
50
- except Exception: # pragma: no cover - platform dependent best effort
51
- pass
52
-
53
23
 
54
24
  @click.group()
55
25
  def config_group() -> None:
@@ -60,13 +30,16 @@ def config_group() -> None:
60
30
  @config_group.command("list")
61
31
  def list_config() -> None:
62
32
  """List current configuration."""
63
-
64
33
  config = load_config()
65
34
 
66
35
  if not config:
67
- console.print(
68
- "[yellow]No configuration found. Run 'aip config configure' to set up.[/yellow]"
69
- )
36
+ hint = command_hint("config configure", slash_command="login")
37
+ if hint:
38
+ console.print(
39
+ f"[yellow]No configuration found. Run '{hint}' to set up.[/yellow]"
40
+ )
41
+ else:
42
+ console.print("[yellow]No configuration found.[/yellow]")
70
43
  return
71
44
 
72
45
  table = AIPTable(title="🔧 AIP Configuration")
@@ -90,7 +63,6 @@ def list_config() -> None:
90
63
  @click.argument("value")
91
64
  def set_config(key: str, value: str) -> None:
92
65
  """Set a configuration value."""
93
-
94
66
  valid_keys = ["api_url", "api_key"]
95
67
 
96
68
  if key not in valid_keys:
@@ -114,11 +86,12 @@ def set_config(key: str, value: str) -> None:
114
86
  @click.argument("key")
115
87
  def get_config(key: str) -> None:
116
88
  """Get a configuration value."""
117
-
118
89
  config = load_config()
119
90
 
120
91
  if key not in config:
121
- console.print(Text(f"[yellow]Configuration key '{key}' not found.[/yellow]"))
92
+ console.print(
93
+ markup_text(f"[yellow]Configuration key '{key}' not found.[/yellow]")
94
+ )
122
95
  raise click.ClickException(f"Configuration key not found: {key}")
123
96
 
124
97
  value = config[key]
@@ -135,11 +108,12 @@ def get_config(key: str) -> None:
135
108
  @click.argument("key")
136
109
  def unset_config(key: str) -> None:
137
110
  """Remove a configuration value."""
138
-
139
111
  config = load_config()
140
112
 
141
113
  if key not in config:
142
- console.print(Text(f"[yellow]Configuration key '{key}' not found.[/yellow]"))
114
+ console.print(
115
+ markup_text(f"[yellow]Configuration key '{key}' not found.[/yellow]")
116
+ )
143
117
  return
144
118
 
145
119
  del config[key]
@@ -152,7 +126,6 @@ def unset_config(key: str) -> None:
152
126
  @click.option("--force", is_flag=True, help="Skip confirmation prompt")
153
127
  def reset_config(force: bool) -> None:
154
128
  """Reset all configuration to defaults."""
155
-
156
129
  if not force:
157
130
  console.print("[yellow]This will remove all AIP configuration.[/yellow]")
158
131
  confirm = input("Are you sure? (y/N): ").strip().lower()
@@ -160,13 +133,28 @@ def reset_config(force: bool) -> None:
160
133
  console.print("Cancelled.")
161
134
  return
162
135
 
163
- if CONFIG_FILE.exists():
164
- CONFIG_FILE.unlink()
165
- console.print(
166
- "✅ Configuration reset. Run 'aip config configure' to set up again."
167
- )
168
- else:
136
+ config_data = load_config()
137
+ file_exists = CONFIG_FILE.exists()
138
+
139
+ if not file_exists and not config_data:
169
140
  console.print("[yellow]No configuration found to reset.[/yellow]")
141
+ console.print("✅ Configuration reset (nothing to remove).")
142
+ return
143
+
144
+ if file_exists:
145
+ try:
146
+ CONFIG_FILE.unlink()
147
+ except FileNotFoundError: # pragma: no cover - defensive cleanup
148
+ pass
149
+ else:
150
+ # In-memory configuration (e.g., tests) needs explicit clearing
151
+ save_config({})
152
+
153
+ hint = command_hint("config configure", slash_command="login")
154
+ message = "✅ Configuration reset."
155
+ if hint:
156
+ message += f" Run '{hint}' to set up again."
157
+ console.print(message)
170
158
 
171
159
 
172
160
  def _configure_interactive() -> None:
@@ -232,11 +220,17 @@ def _configure_interactive() -> None:
232
220
  except Exception as e:
233
221
  console.print(Text(f"❌ Connection failed: {e}"))
234
222
  console.print(" Please check your API URL and key")
235
- console.print(" You can run 'aip status' later to test again")
223
+ hint_status = command_hint("status", slash_command="status")
224
+ if hint_status:
225
+ console.print(f" You can run '{hint_status}' later to test again")
236
226
 
237
227
  console.print("\n💡 You can now use AIP CLI commands!")
238
- console.print(" • Run 'aip status' to check connection")
239
- console.print(" • Run 'aip agents list' to see your agents")
228
+ hint_status = command_hint("status", slash_command="status")
229
+ if hint_status:
230
+ console.print(f" • Run '{hint_status}' to check connection")
231
+ hint_agents = command_hint("agents list", slash_command="agents")
232
+ if hint_agents:
233
+ console.print(f" • Run '{hint_agents}' to see your agents")
240
234
 
241
235
 
242
236
  @config_group.command()
@@ -11,7 +11,6 @@ from typing import Any
11
11
 
12
12
  import click
13
13
  from rich.console import Console
14
- from rich.text import Text
15
14
 
16
15
  from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
17
16
  from glaip_sdk.cli.display import (
@@ -33,6 +32,7 @@ from glaip_sdk.cli.mcp_validators import (
33
32
  )
34
33
  from glaip_sdk.cli.parsers.json_input import parse_json_input
35
34
  from glaip_sdk.cli.resolution import resolve_resource_reference
35
+ from glaip_sdk.cli.rich_helpers import print_markup
36
36
  from glaip_sdk.cli.utils import (
37
37
  coerce_to_row,
38
38
  get_client,
@@ -481,21 +481,23 @@ def _handle_mcp_export(
481
481
  ):
482
482
  mcp = client.mcps.get_mcp_by_id(mcp.id)
483
483
  except Exception as e:
484
- console.print(
485
- Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
484
+ print_markup(
485
+ f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]",
486
+ console=console,
487
+ )
488
+ print_markup(
489
+ "[yellow]⚠️ Proceeding with available data[/yellow]", console=console
486
490
  )
487
- console.print(Text("[yellow]⚠️ Proceeding with available data[/yellow]"))
488
491
 
489
492
  # Determine if we should prompt for secrets
490
493
  prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
491
494
 
492
495
  # Warn user if non-interactive mode forces placeholder usage
493
496
  if not no_auth_prompt and not sys.stdin.isatty():
494
- console.print(
495
- Text(
496
- "[yellow]⚠️ Non-interactive mode detected. "
497
- "Using placeholder values for secrets.[/yellow]"
498
- )
497
+ print_markup(
498
+ "[yellow]⚠️ Non-interactive mode detected. "
499
+ "Using placeholder values for secrets.[/yellow]",
500
+ console=console,
499
501
  )
500
502
 
501
503
  # Build and write export payload
@@ -528,11 +530,10 @@ def _handle_mcp_export(
528
530
  )
529
531
  write_resource_export(export_path, export_payload, detected_format)
530
532
 
531
- console.print(
532
- Text(
533
- f"[green]✅ Complete MCP configuration exported to: "
534
- f"{export_path} (format: {detected_format})[/green]"
535
- )
533
+ print_markup(
534
+ f"[green]✅ Complete MCP configuration exported to: "
535
+ f"{export_path} (format: {detected_format})[/green]",
536
+ console=console,
536
537
  )
537
538
 
538
539
 
@@ -582,9 +583,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
582
583
  "status": getattr(mcp, "status", "N/A"),
583
584
  "connection_status": getattr(mcp, "connection_status", "N/A"),
584
585
  }
585
- output_result(
586
- ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
587
- )
586
+ output_result(ctx, result_data, title=f"🔌 {mcp.name}")
588
587
 
589
588
 
590
589
  @mcps_group.command()
@@ -734,11 +733,9 @@ def connect(ctx: Any, config_file: str) -> None:
734
733
 
735
734
  view = get_ctx_value(ctx, "view", "rich")
736
735
  if view != "json":
737
- console.print(
738
- Text(
739
- f"[yellow]Connecting to MCP with config from "
740
- f"{config_file}...[/yellow]"
741
- )
736
+ print_markup(
737
+ f"[yellow]Connecting to MCP with config from {config_file}...[/yellow]",
738
+ console=console,
742
739
  )
743
740
 
744
741
  # Test connection using config