glaip-sdk 0.0.14__py3-none-any.whl → 0.0.16__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 +27 -1
- glaip_sdk/cli/commands/agents.py +27 -20
- glaip_sdk/cli/commands/configure.py +39 -50
- glaip_sdk/cli/commands/mcps.py +2 -6
- glaip_sdk/cli/commands/models.py +1 -1
- glaip_sdk/cli/commands/tools.py +1 -3
- glaip_sdk/cli/config.py +42 -0
- glaip_sdk/cli/context.py +142 -0
- glaip_sdk/cli/display.py +92 -26
- glaip_sdk/cli/main.py +141 -124
- glaip_sdk/cli/masking.py +148 -0
- glaip_sdk/cli/mcp_validators.py +2 -2
- glaip_sdk/cli/pager.py +272 -0
- glaip_sdk/cli/parsers/json_input.py +2 -2
- glaip_sdk/cli/resolution.py +12 -10
- glaip_sdk/cli/slash/agent_session.py +7 -0
- glaip_sdk/cli/slash/prompt.py +21 -2
- glaip_sdk/cli/slash/session.py +15 -21
- glaip_sdk/cli/update_notifier.py +8 -2
- glaip_sdk/cli/utils.py +99 -369
- glaip_sdk/client/_agent_payloads.py +504 -0
- glaip_sdk/client/agents.py +194 -551
- glaip_sdk/client/base.py +92 -20
- glaip_sdk/client/main.py +6 -0
- glaip_sdk/client/run_rendering.py +275 -0
- glaip_sdk/config/constants.py +3 -0
- glaip_sdk/exceptions.py +15 -0
- glaip_sdk/models.py +5 -0
- glaip_sdk/payload_schemas/__init__.py +19 -0
- glaip_sdk/payload_schemas/agent.py +87 -0
- glaip_sdk/rich_components.py +12 -0
- glaip_sdk/utils/client_utils.py +12 -0
- glaip_sdk/utils/import_export.py +2 -2
- glaip_sdk/utils/rendering/formatting.py +5 -0
- glaip_sdk/utils/rendering/models.py +22 -0
- glaip_sdk/utils/rendering/renderer/base.py +9 -1
- glaip_sdk/utils/rendering/renderer/panels.py +0 -1
- glaip_sdk/utils/rendering/steps.py +59 -0
- glaip_sdk/utils/serialization.py +24 -3
- {glaip_sdk-0.0.14.dist-info → glaip_sdk-0.0.16.dist-info}/METADATA +1 -1
- glaip_sdk-0.0.16.dist-info/RECORD +72 -0
- glaip_sdk-0.0.14.dist-info/RECORD +0 -64
- {glaip_sdk-0.0.14.dist-info → glaip_sdk-0.0.16.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.14.dist-info → glaip_sdk-0.0.16.dist-info}/entry_points.txt +0 -0
glaip_sdk/branding.py
CHANGED
|
@@ -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)
|
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -23,6 +23,7 @@ from glaip_sdk.cli.agent_config import (
|
|
|
23
23
|
from glaip_sdk.cli.agent_config import (
|
|
24
24
|
sanitize_agent_config_for_cli as sanitize_agent_config,
|
|
25
25
|
)
|
|
26
|
+
from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
|
|
26
27
|
from glaip_sdk.cli.display import (
|
|
27
28
|
build_resource_result_data,
|
|
28
29
|
display_agent_run_suggestions,
|
|
@@ -48,10 +49,7 @@ from glaip_sdk.cli.utils import (
|
|
|
48
49
|
_fuzzy_pick_for_resources,
|
|
49
50
|
build_renderer,
|
|
50
51
|
coerce_to_row,
|
|
51
|
-
detect_export_format,
|
|
52
52
|
get_client,
|
|
53
|
-
get_ctx_value,
|
|
54
|
-
output_flags,
|
|
55
53
|
output_list,
|
|
56
54
|
output_result,
|
|
57
55
|
spinner_context,
|
|
@@ -80,7 +78,6 @@ AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
|
80
78
|
|
|
81
79
|
def _safe_agent_attribute(agent: Any, name: str) -> Any:
|
|
82
80
|
"""Return attribute value for ``name`` while filtering Mock sentinels."""
|
|
83
|
-
|
|
84
81
|
try:
|
|
85
82
|
value = getattr(agent, name)
|
|
86
83
|
except Exception:
|
|
@@ -93,7 +90,6 @@ def _safe_agent_attribute(agent: Any, name: str) -> Any:
|
|
|
93
90
|
|
|
94
91
|
def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
|
|
95
92
|
"""Convert a mapping-like candidate to a plain dict when possible."""
|
|
96
|
-
|
|
97
93
|
if candidate is None:
|
|
98
94
|
return None
|
|
99
95
|
if isinstance(candidate, Mapping):
|
|
@@ -103,7 +99,6 @@ def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
|
|
|
103
99
|
|
|
104
100
|
def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
|
|
105
101
|
"""Attempt to call the named method and coerce its output to a dict."""
|
|
106
|
-
|
|
107
102
|
method = getattr(agent, method_name, None)
|
|
108
103
|
if not callable(method):
|
|
109
104
|
return None
|
|
@@ -116,7 +111,6 @@ def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
|
|
|
116
111
|
|
|
117
112
|
def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
|
|
118
113
|
"""Try standard serialisation helpers to produce a mapping."""
|
|
119
|
-
|
|
120
114
|
for attr in ("model_dump", "dict", "to_dict"):
|
|
121
115
|
mapping = _call_agent_method(agent, attr)
|
|
122
116
|
if mapping is not None:
|
|
@@ -126,7 +120,6 @@ def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
|
|
|
126
120
|
|
|
127
121
|
def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
|
|
128
122
|
"""Construct a minimal mapping from well-known agent attributes."""
|
|
129
|
-
|
|
130
123
|
fallback_fields = (
|
|
131
124
|
"id",
|
|
132
125
|
"name",
|
|
@@ -138,6 +131,7 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
|
|
|
138
131
|
"agents",
|
|
139
132
|
"mcps",
|
|
140
133
|
"timeout",
|
|
134
|
+
"tool_configs",
|
|
141
135
|
)
|
|
142
136
|
|
|
143
137
|
fallback: dict[str, Any] = {}
|
|
@@ -151,7 +145,6 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
|
|
|
151
145
|
|
|
152
146
|
def _prepare_agent_output(agent: Any) -> dict[str, Any]:
|
|
153
147
|
"""Build a JSON-serialisable mapping for CLI output."""
|
|
154
|
-
|
|
155
148
|
method_mapping = _coerce_agent_via_methods(agent)
|
|
156
149
|
if method_mapping is not None:
|
|
157
150
|
return method_mapping
|
|
@@ -258,11 +251,11 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
|
|
|
258
251
|
"metadata",
|
|
259
252
|
"language_model_id",
|
|
260
253
|
"agent_config",
|
|
261
|
-
"tool_configs",
|
|
262
254
|
"tools",
|
|
263
255
|
"agents",
|
|
264
256
|
"mcps",
|
|
265
257
|
"a2a_profile",
|
|
258
|
+
"tool_configs",
|
|
266
259
|
]
|
|
267
260
|
|
|
268
261
|
result_data = build_resource_result_data(full_agent, fields)
|
|
@@ -299,8 +292,7 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
299
292
|
output_result(
|
|
300
293
|
ctx,
|
|
301
294
|
formatted_data,
|
|
302
|
-
title=
|
|
303
|
-
panel_title=panel_title,
|
|
295
|
+
title=panel_title,
|
|
304
296
|
)
|
|
305
297
|
else:
|
|
306
298
|
# Fall back to Pydantic model data if raw fetch fails
|
|
@@ -320,7 +312,6 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
320
312
|
ctx,
|
|
321
313
|
result_data,
|
|
322
314
|
title="Agent Details",
|
|
323
|
-
panel_title=f"🤖 {result_data.get('name', 'Unknown')}",
|
|
324
315
|
)
|
|
325
316
|
|
|
326
317
|
|
|
@@ -340,7 +331,14 @@ def _resolve_agent(
|
|
|
340
331
|
"""Resolve agent reference (ID or name) with ambiguity handling.
|
|
341
332
|
|
|
342
333
|
Args:
|
|
343
|
-
|
|
334
|
+
ctx: Click context object for CLI operations.
|
|
335
|
+
client: AIP client instance for API operations.
|
|
336
|
+
ref: Agent reference (ID or name) to resolve.
|
|
337
|
+
select: Pre-selected agent index for non-interactive mode.
|
|
338
|
+
interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Resolved agent object or None if not found.
|
|
344
342
|
"""
|
|
345
343
|
return resolve_resource_reference(
|
|
346
344
|
ctx,
|
|
@@ -880,7 +878,6 @@ def _add_import_file_attributes(
|
|
|
880
878
|
"type",
|
|
881
879
|
"framework",
|
|
882
880
|
"version",
|
|
883
|
-
"tool_configs",
|
|
884
881
|
"mcps",
|
|
885
882
|
"a2a_profile",
|
|
886
883
|
}
|
|
@@ -1052,6 +1049,7 @@ def _handle_update_import_file(
|
|
|
1052
1049
|
instruction: str | None,
|
|
1053
1050
|
tools: tuple[str, ...] | None,
|
|
1054
1051
|
agents: tuple[str, ...] | None,
|
|
1052
|
+
mcps: tuple[str, ...] | None,
|
|
1055
1053
|
timeout: float | None,
|
|
1056
1054
|
) -> tuple[
|
|
1057
1055
|
Any | None,
|
|
@@ -1059,11 +1057,12 @@ def _handle_update_import_file(
|
|
|
1059
1057
|
str | None,
|
|
1060
1058
|
tuple[str, ...] | None,
|
|
1061
1059
|
tuple[str, ...] | None,
|
|
1060
|
+
tuple[str, ...] | None,
|
|
1062
1061
|
float | None,
|
|
1063
1062
|
]:
|
|
1064
1063
|
"""Handle import file processing for agent update."""
|
|
1065
1064
|
if not import_file:
|
|
1066
|
-
return None, name, instruction, tools, agents, timeout
|
|
1065
|
+
return None, name, instruction, tools, agents, mcps, timeout
|
|
1067
1066
|
|
|
1068
1067
|
import_data = load_resource_from_file(Path(import_file), "agent")
|
|
1069
1068
|
import_data = convert_export_to_import_format(import_data)
|
|
@@ -1074,6 +1073,7 @@ def _handle_update_import_file(
|
|
|
1074
1073
|
"instruction": instruction,
|
|
1075
1074
|
"tools": tools or (),
|
|
1076
1075
|
"agents": agents or (),
|
|
1076
|
+
"mcps": mcps or (),
|
|
1077
1077
|
"timeout": timeout,
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
@@ -1085,6 +1085,7 @@ def _handle_update_import_file(
|
|
|
1085
1085
|
merged_data.get("instruction"),
|
|
1086
1086
|
tuple(merged_data.get("tools", ())),
|
|
1087
1087
|
tuple(merged_data.get("agents", ())),
|
|
1088
|
+
tuple(merged_data.get("mcps", ())),
|
|
1088
1089
|
coerce_timeout(merged_data.get("timeout")),
|
|
1089
1090
|
)
|
|
1090
1091
|
|
|
@@ -1094,6 +1095,7 @@ def _build_update_data(
|
|
|
1094
1095
|
instruction: str | None,
|
|
1095
1096
|
tools: tuple[str, ...] | None,
|
|
1096
1097
|
agents: tuple[str, ...] | None,
|
|
1098
|
+
mcps: tuple[str, ...] | None,
|
|
1097
1099
|
timeout: float | None,
|
|
1098
1100
|
) -> dict[str, Any]:
|
|
1099
1101
|
"""Build the update data dictionary from provided parameters."""
|
|
@@ -1106,6 +1108,8 @@ def _build_update_data(
|
|
|
1106
1108
|
update_data["tools"] = list(tools)
|
|
1107
1109
|
if agents:
|
|
1108
1110
|
update_data["agents"] = list(agents)
|
|
1111
|
+
if mcps:
|
|
1112
|
+
update_data["mcps"] = list(mcps)
|
|
1109
1113
|
if timeout is not None:
|
|
1110
1114
|
update_data["timeout"] = timeout
|
|
1111
1115
|
return update_data
|
|
@@ -1143,8 +1147,6 @@ def _handle_update_import_config(
|
|
|
1143
1147
|
"type",
|
|
1144
1148
|
"framework",
|
|
1145
1149
|
"version",
|
|
1146
|
-
"tool_configs",
|
|
1147
|
-
"mcps",
|
|
1148
1150
|
"a2a_profile",
|
|
1149
1151
|
}
|
|
1150
1152
|
for key, value in merged_data.items():
|
|
@@ -1158,6 +1160,7 @@ def _handle_update_import_config(
|
|
|
1158
1160
|
@click.option("--instruction", help="New instruction")
|
|
1159
1161
|
@click.option("--tools", multiple=True, help="New tool names or IDs")
|
|
1160
1162
|
@click.option("--agents", multiple=True, help="New sub-agent names")
|
|
1163
|
+
@click.option("--mcps", multiple=True, help="New MCP names or IDs")
|
|
1161
1164
|
@click.option("--timeout", type=int, help="New timeout value")
|
|
1162
1165
|
@click.option(
|
|
1163
1166
|
"--import",
|
|
@@ -1174,6 +1177,7 @@ def update(
|
|
|
1174
1177
|
instruction: str | None,
|
|
1175
1178
|
tools: tuple[str, ...] | None,
|
|
1176
1179
|
agents: tuple[str, ...] | None,
|
|
1180
|
+
mcps: tuple[str, ...] | None,
|
|
1177
1181
|
timeout: float | None,
|
|
1178
1182
|
import_file: str | None,
|
|
1179
1183
|
) -> None:
|
|
@@ -1194,12 +1198,15 @@ def update(
|
|
|
1194
1198
|
instruction,
|
|
1195
1199
|
tools,
|
|
1196
1200
|
agents,
|
|
1201
|
+
mcps,
|
|
1197
1202
|
timeout,
|
|
1198
1203
|
) = _handle_update_import_file(
|
|
1199
|
-
import_file, name, instruction, tools, agents, timeout
|
|
1204
|
+
import_file, name, instruction, tools, agents, mcps, timeout
|
|
1200
1205
|
)
|
|
1201
1206
|
|
|
1202
|
-
update_data = _build_update_data(
|
|
1207
|
+
update_data = _build_update_data(
|
|
1208
|
+
name, instruction, tools, agents, mcps, timeout
|
|
1209
|
+
)
|
|
1203
1210
|
|
|
1204
1211
|
if merged_data:
|
|
1205
1212
|
_handle_update_import_config(import_file, merged_data, update_data)
|
|
@@ -5,51 +5,20 @@ 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.utils import command_hint
|
|
20
18
|
from glaip_sdk.rich_components import AIPTable
|
|
21
19
|
|
|
22
20
|
console = Console()
|
|
23
21
|
|
|
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
22
|
|
|
54
23
|
@click.group()
|
|
55
24
|
def config_group() -> None:
|
|
@@ -60,13 +29,16 @@ def config_group() -> None:
|
|
|
60
29
|
@config_group.command("list")
|
|
61
30
|
def list_config() -> None:
|
|
62
31
|
"""List current configuration."""
|
|
63
|
-
|
|
64
32
|
config = load_config()
|
|
65
33
|
|
|
66
34
|
if not config:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
35
|
+
hint = command_hint("config configure", slash_command="login")
|
|
36
|
+
if hint:
|
|
37
|
+
console.print(
|
|
38
|
+
f"[yellow]No configuration found. Run '{hint}' to set up.[/yellow]"
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
console.print("[yellow]No configuration found.[/yellow]")
|
|
70
42
|
return
|
|
71
43
|
|
|
72
44
|
table = AIPTable(title="🔧 AIP Configuration")
|
|
@@ -90,7 +62,6 @@ def list_config() -> None:
|
|
|
90
62
|
@click.argument("value")
|
|
91
63
|
def set_config(key: str, value: str) -> None:
|
|
92
64
|
"""Set a configuration value."""
|
|
93
|
-
|
|
94
65
|
valid_keys = ["api_url", "api_key"]
|
|
95
66
|
|
|
96
67
|
if key not in valid_keys:
|
|
@@ -114,7 +85,6 @@ def set_config(key: str, value: str) -> None:
|
|
|
114
85
|
@click.argument("key")
|
|
115
86
|
def get_config(key: str) -> None:
|
|
116
87
|
"""Get a configuration value."""
|
|
117
|
-
|
|
118
88
|
config = load_config()
|
|
119
89
|
|
|
120
90
|
if key not in config:
|
|
@@ -135,7 +105,6 @@ def get_config(key: str) -> None:
|
|
|
135
105
|
@click.argument("key")
|
|
136
106
|
def unset_config(key: str) -> None:
|
|
137
107
|
"""Remove a configuration value."""
|
|
138
|
-
|
|
139
108
|
config = load_config()
|
|
140
109
|
|
|
141
110
|
if key not in config:
|
|
@@ -152,7 +121,6 @@ def unset_config(key: str) -> None:
|
|
|
152
121
|
@click.option("--force", is_flag=True, help="Skip confirmation prompt")
|
|
153
122
|
def reset_config(force: bool) -> None:
|
|
154
123
|
"""Reset all configuration to defaults."""
|
|
155
|
-
|
|
156
124
|
if not force:
|
|
157
125
|
console.print("[yellow]This will remove all AIP configuration.[/yellow]")
|
|
158
126
|
confirm = input("Are you sure? (y/N): ").strip().lower()
|
|
@@ -160,13 +128,28 @@ def reset_config(force: bool) -> None:
|
|
|
160
128
|
console.print("Cancelled.")
|
|
161
129
|
return
|
|
162
130
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
else:
|
|
131
|
+
config_data = load_config()
|
|
132
|
+
file_exists = CONFIG_FILE.exists()
|
|
133
|
+
|
|
134
|
+
if not file_exists and not config_data:
|
|
169
135
|
console.print("[yellow]No configuration found to reset.[/yellow]")
|
|
136
|
+
console.print("✅ Configuration reset (nothing to remove).")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
if file_exists:
|
|
140
|
+
try:
|
|
141
|
+
CONFIG_FILE.unlink()
|
|
142
|
+
except FileNotFoundError: # pragma: no cover - defensive cleanup
|
|
143
|
+
pass
|
|
144
|
+
else:
|
|
145
|
+
# In-memory configuration (e.g., tests) needs explicit clearing
|
|
146
|
+
save_config({})
|
|
147
|
+
|
|
148
|
+
hint = command_hint("config configure", slash_command="login")
|
|
149
|
+
message = "✅ Configuration reset."
|
|
150
|
+
if hint:
|
|
151
|
+
message += f" Run '{hint}' to set up again."
|
|
152
|
+
console.print(message)
|
|
170
153
|
|
|
171
154
|
|
|
172
155
|
def _configure_interactive() -> None:
|
|
@@ -232,11 +215,17 @@ def _configure_interactive() -> None:
|
|
|
232
215
|
except Exception as e:
|
|
233
216
|
console.print(Text(f"❌ Connection failed: {e}"))
|
|
234
217
|
console.print(" Please check your API URL and key")
|
|
235
|
-
|
|
218
|
+
hint_status = command_hint("status", slash_command="status")
|
|
219
|
+
if hint_status:
|
|
220
|
+
console.print(f" You can run '{hint_status}' later to test again")
|
|
236
221
|
|
|
237
222
|
console.print("\n💡 You can now use AIP CLI commands!")
|
|
238
|
-
|
|
239
|
-
|
|
223
|
+
hint_status = command_hint("status", slash_command="status")
|
|
224
|
+
if hint_status:
|
|
225
|
+
console.print(f" • Run '{hint_status}' to check connection")
|
|
226
|
+
hint_agents = command_hint("agents list", slash_command="agents")
|
|
227
|
+
if hint_agents:
|
|
228
|
+
console.print(f" • Run '{hint_agents}' to see your agents")
|
|
240
229
|
|
|
241
230
|
|
|
242
231
|
@config_group.command()
|
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -13,6 +13,7 @@ import click
|
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
from rich.text import Text
|
|
15
15
|
|
|
16
|
+
from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
|
|
16
17
|
from glaip_sdk.cli.display import (
|
|
17
18
|
display_api_error,
|
|
18
19
|
display_confirmation_prompt,
|
|
@@ -34,10 +35,7 @@ from glaip_sdk.cli.parsers.json_input import parse_json_input
|
|
|
34
35
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
35
36
|
from glaip_sdk.cli.utils import (
|
|
36
37
|
coerce_to_row,
|
|
37
|
-
detect_export_format,
|
|
38
38
|
get_client,
|
|
39
|
-
get_ctx_value,
|
|
40
|
-
output_flags,
|
|
41
39
|
output_list,
|
|
42
40
|
output_result,
|
|
43
41
|
spinner_context,
|
|
@@ -584,9 +582,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
|
584
582
|
"status": getattr(mcp, "status", "N/A"),
|
|
585
583
|
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
586
584
|
}
|
|
587
|
-
output_result(
|
|
588
|
-
ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
|
|
589
|
-
)
|
|
585
|
+
output_result(ctx, result_data, title=f"🔌 {mcp.name}")
|
|
590
586
|
|
|
591
587
|
|
|
592
588
|
@mcps_group.command()
|
glaip_sdk/cli/commands/models.py
CHANGED
glaip_sdk/cli/commands/tools.py
CHANGED
|
@@ -13,6 +13,7 @@ import click
|
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
from rich.text import Text
|
|
15
15
|
|
|
16
|
+
from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
|
|
16
17
|
from glaip_sdk.cli.display import (
|
|
17
18
|
display_api_error,
|
|
18
19
|
display_confirmation_prompt,
|
|
@@ -34,10 +35,7 @@ from glaip_sdk.cli.io import (
|
|
|
34
35
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
35
36
|
from glaip_sdk.cli.utils import (
|
|
36
37
|
coerce_to_row,
|
|
37
|
-
detect_export_format,
|
|
38
38
|
get_client,
|
|
39
|
-
get_ctx_value,
|
|
40
|
-
output_flags,
|
|
41
39
|
output_list,
|
|
42
40
|
output_result,
|
|
43
41
|
spinner_context,
|
glaip_sdk/cli/config.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Configuration management utilities.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
CONFIG_DIR = Path.home() / ".aip"
|
|
14
|
+
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_config() -> dict[str, Any]:
|
|
18
|
+
"""Load configuration from file."""
|
|
19
|
+
if not CONFIG_FILE.exists():
|
|
20
|
+
return {}
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with open(CONFIG_FILE) as f:
|
|
24
|
+
return yaml.safe_load(f) or {}
|
|
25
|
+
except yaml.YAMLError:
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def save_config(config: dict[str, Any]) -> None:
|
|
30
|
+
"""Save configuration to file."""
|
|
31
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
32
|
+
|
|
33
|
+
with open(CONFIG_FILE, "w") as f:
|
|
34
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
35
|
+
|
|
36
|
+
# Set secure file permissions
|
|
37
|
+
try:
|
|
38
|
+
os.chmod(CONFIG_FILE, 0o600)
|
|
39
|
+
except (
|
|
40
|
+
OSError
|
|
41
|
+
): # pragma: no cover - permission errors are expected in some environments
|
|
42
|
+
pass
|
glaip_sdk/cli/context.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Context-related helpers for the glaip CLI.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"get_ctx_value",
|
|
17
|
+
"_get_view",
|
|
18
|
+
"_set_view",
|
|
19
|
+
"_set_json",
|
|
20
|
+
"output_flags",
|
|
21
|
+
"detect_export_format",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_ctx_value(ctx: Any, key: str, default: Any = None) -> Any:
|
|
26
|
+
"""Safely resolve a value from a click context object.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
ctx: Click context object to extract value from
|
|
30
|
+
key: Key to retrieve from the context
|
|
31
|
+
default: Default value if key is not found
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
The value associated with the key, or the default if not found
|
|
35
|
+
"""
|
|
36
|
+
if ctx is None:
|
|
37
|
+
return default
|
|
38
|
+
|
|
39
|
+
obj = getattr(ctx, "obj", None)
|
|
40
|
+
if obj is None:
|
|
41
|
+
return default
|
|
42
|
+
|
|
43
|
+
if isinstance(obj, dict):
|
|
44
|
+
return obj.get(key, default)
|
|
45
|
+
|
|
46
|
+
getter = getattr(obj, "get", None)
|
|
47
|
+
if callable(getter):
|
|
48
|
+
try:
|
|
49
|
+
return getter(key, default)
|
|
50
|
+
except TypeError:
|
|
51
|
+
return default
|
|
52
|
+
|
|
53
|
+
return getattr(obj, key, default) if hasattr(obj, key) else default
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _get_view(ctx: Any) -> str:
|
|
57
|
+
"""Resolve the active view preference from context.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
ctx: Click context object containing view preferences
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The view format string (rich, plain, json, md), defaults to 'rich'
|
|
64
|
+
"""
|
|
65
|
+
view = get_ctx_value(ctx, "view")
|
|
66
|
+
if view:
|
|
67
|
+
return view
|
|
68
|
+
|
|
69
|
+
fallback = get_ctx_value(ctx, "format")
|
|
70
|
+
return fallback or "rich"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _set_view(ctx: Any, _param: Any, value: str) -> None:
|
|
74
|
+
"""Click callback to persist the `--view/--output` option.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
ctx: Click context object to store the view preference
|
|
78
|
+
_param: Click parameter object (unused)
|
|
79
|
+
value: The view format string to store
|
|
80
|
+
"""
|
|
81
|
+
if not value:
|
|
82
|
+
return
|
|
83
|
+
ctx.ensure_object(dict)
|
|
84
|
+
ctx.obj["view"] = value
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _set_json(ctx: Any, _param: Any, value: bool) -> None:
|
|
88
|
+
"""Click callback for the `--json` shorthand flag.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
ctx: Click context object to store the view preference
|
|
92
|
+
_param: Click parameter object (unused)
|
|
93
|
+
value: Boolean flag indicating json mode
|
|
94
|
+
"""
|
|
95
|
+
if not value:
|
|
96
|
+
return
|
|
97
|
+
ctx.ensure_object(dict)
|
|
98
|
+
ctx.obj["view"] = "json"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def output_flags() -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
102
|
+
"""Decorator to add shared output flags (`--view`, `--json`) to commands.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A decorator function that adds output format options to click commands
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
109
|
+
f = click.option(
|
|
110
|
+
"--json",
|
|
111
|
+
"json_mode",
|
|
112
|
+
is_flag=True,
|
|
113
|
+
expose_value=False,
|
|
114
|
+
help="Shortcut for --view json",
|
|
115
|
+
callback=_set_json,
|
|
116
|
+
)(f)
|
|
117
|
+
f = click.option(
|
|
118
|
+
"-o",
|
|
119
|
+
"--output",
|
|
120
|
+
"--view",
|
|
121
|
+
"view_opt",
|
|
122
|
+
type=click.Choice(["rich", "plain", "json", "md"]),
|
|
123
|
+
expose_value=False,
|
|
124
|
+
help="Output format",
|
|
125
|
+
callback=_set_view,
|
|
126
|
+
)(f)
|
|
127
|
+
return f
|
|
128
|
+
|
|
129
|
+
return decorator
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def detect_export_format(file_path: str | Path) -> str:
|
|
133
|
+
"""Detect the export format from the file extension.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
file_path: Path to the file to analyze
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The format string ('yaml' or 'json') based on file extension
|
|
140
|
+
"""
|
|
141
|
+
path = Path(file_path)
|
|
142
|
+
return "yaml" if path.suffix.lower() in {".yaml", ".yml"} else "json"
|