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 +3 -3
- glaip_sdk/cli/commands/agents.py +119 -12
- glaip_sdk/cli/commands/mcps.py +61 -15
- glaip_sdk/cli/commands/models.py +12 -2
- glaip_sdk/cli/commands/tools.py +69 -13
- glaip_sdk/cli/display.py +4 -3
- glaip_sdk/cli/main.py +54 -5
- glaip_sdk/cli/resolution.py +17 -9
- glaip_sdk/cli/slash/__init__.py +25 -0
- glaip_sdk/cli/slash/agent_session.py +146 -0
- glaip_sdk/cli/slash/prompt.py +198 -0
- glaip_sdk/cli/slash/session.py +665 -0
- glaip_sdk/cli/utils.py +150 -12
- glaip_sdk/utils/rendering/renderer/base.py +20 -2
- glaip_sdk/utils/rendering/renderer/panels.py +21 -7
- glaip_sdk/utils/serialization.py +49 -17
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/METADATA +2 -2
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/RECORD +20 -16
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/entry_points.txt +0 -0
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 = "
|
|
35
|
-
BORDER =
|
|
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
|
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
glaip_sdk/cli/commands/models.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 = [
|
glaip_sdk/cli/commands/tools.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"🚀
|
|
251
|
-
f
|
|
252
|
-
f"📋
|
|
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.
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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")
|