glaip-sdk 0.0.9__py3-none-any.whl → 0.0.11__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 +8 -4
- glaip_sdk/cli/auth.py +414 -0
- glaip_sdk/cli/commands/agents.py +25 -3
- glaip_sdk/cli/commands/mcps.py +276 -92
- glaip_sdk/cli/main.py +8 -0
- glaip_sdk/cli/slash/agent_session.py +46 -2
- glaip_sdk/cli/slash/prompt.py +48 -10
- glaip_sdk/cli/slash/session.py +258 -68
- glaip_sdk/cli/update_notifier.py +107 -0
- glaip_sdk/cli/utils.py +34 -8
- glaip_sdk/config/constants.py +0 -4
- glaip_sdk/exceptions.py +0 -6
- glaip_sdk/models.py +0 -2
- glaip_sdk/utils/import_export.py +3 -7
- glaip_sdk/utils/rendering/renderer/base.py +105 -45
- glaip_sdk/utils/rendering/renderer/config.py +2 -2
- glaip_sdk/utils/serialization.py +93 -2
- {glaip_sdk-0.0.9.dist-info → glaip_sdk-0.0.11.dist-info}/METADATA +2 -1
- {glaip_sdk-0.0.9.dist-info → glaip_sdk-0.0.11.dist-info}/RECORD +21 -19
- {glaip_sdk-0.0.9.dist-info → glaip_sdk-0.0.11.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.9.dist-info → glaip_sdk-0.0.11.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -5,6 +5,7 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
+
import sys
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
@@ -21,9 +22,6 @@ from glaip_sdk.cli.display import (
|
|
|
21
22
|
handle_json_output,
|
|
22
23
|
handle_rich_output,
|
|
23
24
|
)
|
|
24
|
-
from glaip_sdk.cli.io import (
|
|
25
|
-
export_resource_to_file_with_validation as export_resource_to_file,
|
|
26
|
-
)
|
|
27
25
|
from glaip_sdk.cli.io import (
|
|
28
26
|
fetch_raw_resource_details,
|
|
29
27
|
)
|
|
@@ -40,20 +38,41 @@ from glaip_sdk.cli.utils import (
|
|
|
40
38
|
)
|
|
41
39
|
from glaip_sdk.rich_components import AIPPanel
|
|
42
40
|
from glaip_sdk.utils import format_datetime
|
|
41
|
+
from glaip_sdk.utils.serialization import (
|
|
42
|
+
build_mcp_export_payload,
|
|
43
|
+
write_resource_export,
|
|
44
|
+
)
|
|
43
45
|
|
|
44
46
|
console = Console()
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
@click.group(name="mcps", no_args_is_help=True)
|
|
48
50
|
def mcps_group() -> None:
|
|
49
|
-
"""MCP management operations.
|
|
51
|
+
"""MCP management operations.
|
|
52
|
+
|
|
53
|
+
Provides commands for creating, listing, updating, deleting, and managing
|
|
54
|
+
Model Context Protocol (MCP) configurations.
|
|
55
|
+
"""
|
|
50
56
|
pass
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
def _resolve_mcp(
|
|
54
60
|
ctx: Any, client: Any, ref: str, select: int | None = None
|
|
55
61
|
) -> Any | None:
|
|
56
|
-
"""Resolve MCP reference (ID or name) with ambiguity handling.
|
|
62
|
+
"""Resolve MCP reference (ID or name) with ambiguity handling.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
ctx: Click context object
|
|
66
|
+
client: API client instance
|
|
67
|
+
ref: MCP reference (ID or name)
|
|
68
|
+
select: Index to select when multiple matches found
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
MCP object if found, None otherwise
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ClickException: If MCP not found or selection invalid
|
|
75
|
+
"""
|
|
57
76
|
return resolve_resource_reference(
|
|
58
77
|
ctx,
|
|
59
78
|
client,
|
|
@@ -70,7 +89,14 @@ def _resolve_mcp(
|
|
|
70
89
|
@output_flags()
|
|
71
90
|
@click.pass_context
|
|
72
91
|
def list_mcps(ctx: Any) -> None:
|
|
73
|
-
"""List all MCPs.
|
|
92
|
+
"""List all MCPs in a formatted table.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
ctx: Click context containing output format preferences
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ClickException: If API request fails
|
|
99
|
+
"""
|
|
74
100
|
try:
|
|
75
101
|
client = get_client(ctx)
|
|
76
102
|
with spinner_context(
|
|
@@ -117,7 +143,18 @@ def list_mcps(ctx: Any) -> None:
|
|
|
117
143
|
def create(
|
|
118
144
|
ctx: Any, name: str, transport: str, description: str | None, config: str | None
|
|
119
145
|
) -> None:
|
|
120
|
-
"""Create a new MCP.
|
|
146
|
+
"""Create a new MCP with specified configuration.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
ctx: Click context containing output format preferences
|
|
150
|
+
name: MCP name (required)
|
|
151
|
+
transport: MCP transport protocol (required)
|
|
152
|
+
description: Optional MCP description
|
|
153
|
+
config: JSON configuration string for MCP settings
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ClickException: If JSON parsing fails or API request fails
|
|
157
|
+
"""
|
|
121
158
|
try:
|
|
122
159
|
client = get_client(ctx)
|
|
123
160
|
|
|
@@ -163,22 +200,191 @@ def create(
|
|
|
163
200
|
raise click.ClickException(str(e))
|
|
164
201
|
|
|
165
202
|
|
|
203
|
+
def _handle_mcp_export(
|
|
204
|
+
ctx: Any,
|
|
205
|
+
client: Any,
|
|
206
|
+
mcp: Any,
|
|
207
|
+
export_path: Path,
|
|
208
|
+
no_auth_prompt: bool,
|
|
209
|
+
auth_placeholder: str,
|
|
210
|
+
) -> None:
|
|
211
|
+
"""Handle MCP export to file with format detection and auth handling.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
ctx: Click context for spinner management
|
|
215
|
+
client: API client for fetching MCP details
|
|
216
|
+
mcp: MCP object to export
|
|
217
|
+
export_path: Target file path (format detected from extension)
|
|
218
|
+
no_auth_prompt: Skip interactive secret prompts if True
|
|
219
|
+
auth_placeholder: Placeholder text for missing secrets
|
|
220
|
+
|
|
221
|
+
Note:
|
|
222
|
+
Supports JSON (.json) and YAML (.yaml/.yml) export formats.
|
|
223
|
+
In interactive mode, prompts for secret values.
|
|
224
|
+
In non-interactive mode, uses placeholder values.
|
|
225
|
+
"""
|
|
226
|
+
# Auto-detect format from file extension
|
|
227
|
+
detected_format = detect_export_format(export_path)
|
|
228
|
+
|
|
229
|
+
# Always export comprehensive data - re-fetch with full details
|
|
230
|
+
try:
|
|
231
|
+
with spinner_context(
|
|
232
|
+
ctx,
|
|
233
|
+
"[bold blue]Fetching complete MCP details…[/bold blue]",
|
|
234
|
+
console_override=console,
|
|
235
|
+
):
|
|
236
|
+
mcp = client.mcps.get_mcp_by_id(mcp.id)
|
|
237
|
+
except Exception as e:
|
|
238
|
+
console.print(
|
|
239
|
+
Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
|
|
240
|
+
)
|
|
241
|
+
console.print(Text("[yellow]⚠️ Proceeding with available data[/yellow]"))
|
|
242
|
+
|
|
243
|
+
# Determine if we should prompt for secrets
|
|
244
|
+
prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
|
|
245
|
+
|
|
246
|
+
# Warn user if non-interactive mode forces placeholder usage
|
|
247
|
+
if not no_auth_prompt and not sys.stdin.isatty():
|
|
248
|
+
console.print(
|
|
249
|
+
Text(
|
|
250
|
+
"[yellow]⚠️ Non-interactive mode detected. "
|
|
251
|
+
"Using placeholder values for secrets.[/yellow]"
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Build and write export payload
|
|
256
|
+
if prompt_for_secrets:
|
|
257
|
+
# Interactive mode: no spinner during prompts
|
|
258
|
+
export_payload = build_mcp_export_payload(
|
|
259
|
+
mcp,
|
|
260
|
+
prompt_for_secrets=prompt_for_secrets,
|
|
261
|
+
placeholder=auth_placeholder,
|
|
262
|
+
console=console,
|
|
263
|
+
)
|
|
264
|
+
with spinner_context(
|
|
265
|
+
ctx,
|
|
266
|
+
"[bold blue]Writing export file…[/bold blue]",
|
|
267
|
+
console_override=console,
|
|
268
|
+
):
|
|
269
|
+
write_resource_export(export_path, export_payload, detected_format)
|
|
270
|
+
else:
|
|
271
|
+
# Non-interactive mode: spinner for entire export process
|
|
272
|
+
with spinner_context(
|
|
273
|
+
ctx,
|
|
274
|
+
"[bold blue]Exporting MCP configuration…[/bold blue]",
|
|
275
|
+
console_override=console,
|
|
276
|
+
):
|
|
277
|
+
export_payload = build_mcp_export_payload(
|
|
278
|
+
mcp,
|
|
279
|
+
prompt_for_secrets=prompt_for_secrets,
|
|
280
|
+
placeholder=auth_placeholder,
|
|
281
|
+
console=console,
|
|
282
|
+
)
|
|
283
|
+
write_resource_export(export_path, export_payload, detected_format)
|
|
284
|
+
|
|
285
|
+
console.print(
|
|
286
|
+
Text(
|
|
287
|
+
f"[green]✅ Complete MCP configuration exported to: "
|
|
288
|
+
f"{export_path} (format: {detected_format})[/green]"
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
294
|
+
"""Display MCP details using raw API data or fallback to Pydantic model.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
ctx: Click context containing output format preferences
|
|
298
|
+
client: API client for fetching raw MCP data
|
|
299
|
+
mcp: MCP object to display details for
|
|
300
|
+
|
|
301
|
+
Note:
|
|
302
|
+
Attempts to fetch raw API data first to preserve all fields.
|
|
303
|
+
Falls back to Pydantic model data if raw data unavailable.
|
|
304
|
+
Formats datetime fields for better readability.
|
|
305
|
+
"""
|
|
306
|
+
# Try to fetch raw API data first to preserve ALL fields
|
|
307
|
+
with spinner_context(
|
|
308
|
+
ctx,
|
|
309
|
+
"[bold blue]Fetching detailed MCP data…[/bold blue]",
|
|
310
|
+
console_override=console,
|
|
311
|
+
):
|
|
312
|
+
raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
|
|
313
|
+
|
|
314
|
+
if raw_mcp_data:
|
|
315
|
+
# Use raw API data - this preserves ALL fields
|
|
316
|
+
formatted_data = raw_mcp_data.copy()
|
|
317
|
+
if "created_at" in formatted_data:
|
|
318
|
+
formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
|
|
319
|
+
if "updated_at" in formatted_data:
|
|
320
|
+
formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
|
|
321
|
+
|
|
322
|
+
output_result(
|
|
323
|
+
ctx,
|
|
324
|
+
formatted_data,
|
|
325
|
+
title="MCP Details",
|
|
326
|
+
panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
# Fall back to Pydantic model data
|
|
330
|
+
console.print("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
331
|
+
result_data = {
|
|
332
|
+
"id": str(getattr(mcp, "id", "N/A")),
|
|
333
|
+
"name": getattr(mcp, "name", "N/A"),
|
|
334
|
+
"type": getattr(mcp, "type", "N/A"),
|
|
335
|
+
"config": getattr(mcp, "config", "N/A"),
|
|
336
|
+
"status": getattr(mcp, "status", "N/A"),
|
|
337
|
+
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
338
|
+
}
|
|
339
|
+
output_result(
|
|
340
|
+
ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
166
344
|
@mcps_group.command()
|
|
167
345
|
@click.argument("mcp_ref")
|
|
168
346
|
@click.option(
|
|
169
347
|
"--export",
|
|
170
348
|
type=click.Path(dir_okay=False, writable=True),
|
|
171
|
-
help="Export complete MCP configuration to file
|
|
349
|
+
help="Export complete MCP configuration to file "
|
|
350
|
+
"(format auto-detected from .json/.yaml extension)",
|
|
351
|
+
)
|
|
352
|
+
@click.option(
|
|
353
|
+
"--no-auth-prompt",
|
|
354
|
+
is_flag=True,
|
|
355
|
+
help="Skip interactive secret prompts and use placeholder values.",
|
|
356
|
+
)
|
|
357
|
+
@click.option(
|
|
358
|
+
"--auth-placeholder",
|
|
359
|
+
default="<INSERT VALUE>",
|
|
360
|
+
show_default=True,
|
|
361
|
+
help="Placeholder text used when secrets are unavailable.",
|
|
172
362
|
)
|
|
173
363
|
@output_flags()
|
|
174
364
|
@click.pass_context
|
|
175
|
-
def get(
|
|
176
|
-
|
|
365
|
+
def get(
|
|
366
|
+
ctx: Any,
|
|
367
|
+
mcp_ref: str,
|
|
368
|
+
export: str | None,
|
|
369
|
+
no_auth_prompt: bool,
|
|
370
|
+
auth_placeholder: str,
|
|
371
|
+
) -> None:
|
|
372
|
+
"""Get MCP details and optionally export configuration to file.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
ctx: Click context containing output format preferences
|
|
376
|
+
mcp_ref: MCP reference (ID or name)
|
|
377
|
+
export: Optional file path to export MCP configuration
|
|
378
|
+
no_auth_prompt: Skip interactive secret prompts if True
|
|
379
|
+
auth_placeholder: Placeholder text for missing secrets
|
|
380
|
+
|
|
381
|
+
Raises:
|
|
382
|
+
ClickException: If MCP not found or export fails
|
|
177
383
|
|
|
178
384
|
Examples:
|
|
179
385
|
aip mcps get my-mcp
|
|
180
|
-
aip mcps get my-mcp --export mcp.json #
|
|
181
|
-
aip mcps get my-mcp --export mcp.yaml #
|
|
386
|
+
aip mcps get my-mcp --export mcp.json # Export as JSON
|
|
387
|
+
aip mcps get my-mcp --export mcp.yaml # Export as YAML
|
|
182
388
|
"""
|
|
183
389
|
try:
|
|
184
390
|
client = get_client(ctx)
|
|
@@ -188,83 +394,12 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
|
188
394
|
|
|
189
395
|
# Handle export option
|
|
190
396
|
if export:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
detected_format = detect_export_format(export_path)
|
|
194
|
-
|
|
195
|
-
# Always export comprehensive data - re-fetch MCP with full details if needed
|
|
196
|
-
try:
|
|
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)
|
|
203
|
-
except Exception as e:
|
|
204
|
-
console.print(
|
|
205
|
-
Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
|
|
206
|
-
)
|
|
207
|
-
console.print(
|
|
208
|
-
Text("[yellow]⚠️ Proceeding with available data[/yellow]")
|
|
209
|
-
)
|
|
210
|
-
|
|
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)
|
|
217
|
-
console.print(
|
|
218
|
-
Text(
|
|
219
|
-
f"[green]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/green]"
|
|
220
|
-
)
|
|
397
|
+
_handle_mcp_export(
|
|
398
|
+
ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder
|
|
221
399
|
)
|
|
222
400
|
|
|
223
|
-
#
|
|
224
|
-
|
|
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")
|
|
230
|
-
|
|
231
|
-
if raw_mcp_data:
|
|
232
|
-
# Use raw API data - this preserves ALL fields
|
|
233
|
-
# Format dates for better display (minimal postprocessing)
|
|
234
|
-
formatted_data = raw_mcp_data.copy()
|
|
235
|
-
if "created_at" in formatted_data:
|
|
236
|
-
formatted_data["created_at"] = format_datetime(
|
|
237
|
-
formatted_data["created_at"]
|
|
238
|
-
)
|
|
239
|
-
if "updated_at" in formatted_data:
|
|
240
|
-
formatted_data["updated_at"] = format_datetime(
|
|
241
|
-
formatted_data["updated_at"]
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
# Display using output_result with raw data
|
|
245
|
-
output_result(
|
|
246
|
-
ctx,
|
|
247
|
-
formatted_data,
|
|
248
|
-
title="MCP Details",
|
|
249
|
-
panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
|
|
250
|
-
)
|
|
251
|
-
else:
|
|
252
|
-
# Fall back to original method if raw fetch fails
|
|
253
|
-
console.print("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
254
|
-
|
|
255
|
-
# Create result data with actual available fields
|
|
256
|
-
result_data = {
|
|
257
|
-
"id": str(getattr(mcp, "id", "N/A")),
|
|
258
|
-
"name": getattr(mcp, "name", "N/A"),
|
|
259
|
-
"type": getattr(mcp, "type", "N/A"),
|
|
260
|
-
"config": getattr(mcp, "config", "N/A"),
|
|
261
|
-
"status": getattr(mcp, "status", "N/A"),
|
|
262
|
-
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
output_result(
|
|
266
|
-
ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
|
|
267
|
-
)
|
|
401
|
+
# Display MCP details
|
|
402
|
+
_display_mcp_details(ctx, client, mcp)
|
|
268
403
|
|
|
269
404
|
except Exception as e:
|
|
270
405
|
raise click.ClickException(str(e))
|
|
@@ -275,7 +410,15 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
|
275
410
|
@output_flags()
|
|
276
411
|
@click.pass_context
|
|
277
412
|
def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
278
|
-
"""List tools from MCP.
|
|
413
|
+
"""List tools available from a specific MCP.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
ctx: Click context containing output format preferences
|
|
417
|
+
mcp_ref: MCP reference (ID or name)
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
ClickException: If MCP not found or tools fetch fails
|
|
421
|
+
"""
|
|
279
422
|
try:
|
|
280
423
|
client = get_client(ctx)
|
|
281
424
|
|
|
@@ -325,7 +468,19 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
325
468
|
@output_flags()
|
|
326
469
|
@click.pass_context
|
|
327
470
|
def connect(ctx: Any, config_file: str) -> None:
|
|
328
|
-
"""
|
|
471
|
+
"""Test MCP connection using a configuration file.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
ctx: Click context containing output format preferences
|
|
475
|
+
config_file: Path to MCP configuration JSON file
|
|
476
|
+
|
|
477
|
+
Raises:
|
|
478
|
+
ClickException: If config file invalid or connection test fails
|
|
479
|
+
|
|
480
|
+
Note:
|
|
481
|
+
Loads MCP configuration from JSON file and tests connectivity.
|
|
482
|
+
Displays success or failure with connection details.
|
|
483
|
+
"""
|
|
329
484
|
try:
|
|
330
485
|
client = get_client(ctx)
|
|
331
486
|
|
|
@@ -337,7 +492,8 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
337
492
|
if view != "json":
|
|
338
493
|
console.print(
|
|
339
494
|
Text(
|
|
340
|
-
f"[yellow]Connecting to MCP with config from
|
|
495
|
+
f"[yellow]Connecting to MCP with config from "
|
|
496
|
+
f"{config_file}...[/yellow]"
|
|
341
497
|
)
|
|
342
498
|
)
|
|
343
499
|
|
|
@@ -379,7 +535,22 @@ def update(
|
|
|
379
535
|
description: str | None,
|
|
380
536
|
config: str | None,
|
|
381
537
|
) -> None:
|
|
382
|
-
"""Update an existing MCP.
|
|
538
|
+
"""Update an existing MCP with new configuration values.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
ctx: Click context containing output format preferences
|
|
542
|
+
mcp_ref: MCP reference (ID or name)
|
|
543
|
+
name: New MCP name (optional)
|
|
544
|
+
description: New description (optional)
|
|
545
|
+
config: New JSON configuration string (optional)
|
|
546
|
+
|
|
547
|
+
Raises:
|
|
548
|
+
ClickException: If MCP not found, JSON invalid, or no fields specified
|
|
549
|
+
|
|
550
|
+
Note:
|
|
551
|
+
At least one field must be specified for update.
|
|
552
|
+
Uses PUT for complete updates or PATCH for partial updates.
|
|
553
|
+
"""
|
|
383
554
|
try:
|
|
384
555
|
client = get_client(ctx)
|
|
385
556
|
|
|
@@ -425,7 +596,20 @@ def update(
|
|
|
425
596
|
@output_flags()
|
|
426
597
|
@click.pass_context
|
|
427
598
|
def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
428
|
-
"""Delete an MCP.
|
|
599
|
+
"""Delete an MCP after confirmation.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
ctx: Click context containing output format preferences
|
|
603
|
+
mcp_ref: MCP reference (ID or name)
|
|
604
|
+
yes: Skip confirmation prompt if True
|
|
605
|
+
|
|
606
|
+
Raises:
|
|
607
|
+
ClickException: If MCP not found or deletion fails
|
|
608
|
+
|
|
609
|
+
Note:
|
|
610
|
+
Requires confirmation unless --yes flag is provided.
|
|
611
|
+
Deletion is permanent and cannot be undone.
|
|
612
|
+
"""
|
|
429
613
|
try:
|
|
430
614
|
client = get_client(ctx)
|
|
431
615
|
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -24,6 +24,7 @@ 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.cli.update_notifier import maybe_notify_update
|
|
27
28
|
from glaip_sdk.cli.utils import spinner_context, update_spinner
|
|
28
29
|
from glaip_sdk.config.constants import (
|
|
29
30
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
@@ -82,6 +83,13 @@ def main(
|
|
|
82
83
|
|
|
83
84
|
ctx.obj["tty"] = not no_tty
|
|
84
85
|
|
|
86
|
+
if not ctx.resilient_parsing and ctx.obj["tty"]:
|
|
87
|
+
console = Console()
|
|
88
|
+
maybe_notify_update(
|
|
89
|
+
_SDK_VERSION,
|
|
90
|
+
console=console,
|
|
91
|
+
)
|
|
92
|
+
|
|
85
93
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
86
94
|
if _should_launch_slash(ctx) and SlashSession is not None:
|
|
87
95
|
session = SlashSession(ctx)
|
|
@@ -12,6 +12,7 @@ import click
|
|
|
12
12
|
|
|
13
13
|
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
14
14
|
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
15
|
+
from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
17
18
|
from .session import SlashSession
|
|
@@ -34,6 +35,7 @@ class AgentRunSession:
|
|
|
34
35
|
"help": "Display this context-aware menu.",
|
|
35
36
|
"exit": "Return to the command palette.",
|
|
36
37
|
"q": "Return to the command palette.",
|
|
38
|
+
"verbose": "Toggle verbose streaming output.",
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
def run(self) -> None:
|
|
@@ -71,8 +73,30 @@ class AgentRunSession:
|
|
|
71
73
|
def _get_user_input(self) -> str | None:
|
|
72
74
|
"""Get user input with proper error handling."""
|
|
73
75
|
try:
|
|
76
|
+
|
|
77
|
+
def _prompt_message() -> Any:
|
|
78
|
+
verbose_enabled = self.session.verbose_enabled
|
|
79
|
+
verbose_tag = "[verbose:on]" if verbose_enabled else "[verbose:off]"
|
|
80
|
+
prompt_prefix = f"{self._agent_name} ({self._agent_id}) "
|
|
81
|
+
|
|
82
|
+
# Use FormattedText if prompt_toolkit is available, otherwise use simple string
|
|
83
|
+
if _HAS_PROMPT_TOOLKIT and FormattedText is not None:
|
|
84
|
+
segments = [
|
|
85
|
+
("class:prompt", prompt_prefix),
|
|
86
|
+
(
|
|
87
|
+
"class:prompt-verbose-on"
|
|
88
|
+
if verbose_enabled
|
|
89
|
+
else "class:prompt-verbose-off",
|
|
90
|
+
verbose_tag,
|
|
91
|
+
),
|
|
92
|
+
("class:prompt", "\n› "),
|
|
93
|
+
]
|
|
94
|
+
return FormattedText(segments)
|
|
95
|
+
|
|
96
|
+
return f"{prompt_prefix}{verbose_tag}\n› "
|
|
97
|
+
|
|
74
98
|
raw = self.session._prompt(
|
|
75
|
-
|
|
99
|
+
_prompt_message,
|
|
76
100
|
placeholder=self._prompt_placeholder,
|
|
77
101
|
)
|
|
78
102
|
if self._prompt_placeholder:
|
|
@@ -138,9 +162,29 @@ class AgentRunSession:
|
|
|
138
162
|
return
|
|
139
163
|
|
|
140
164
|
try:
|
|
165
|
+
ctx = self.session.ctx
|
|
166
|
+
ctx_obj = getattr(ctx, "obj", None)
|
|
167
|
+
previous_session = None
|
|
168
|
+
if isinstance(ctx_obj, dict):
|
|
169
|
+
previous_session = ctx_obj.get("_slash_session")
|
|
170
|
+
ctx_obj["_slash_session"] = self.session
|
|
171
|
+
|
|
172
|
+
self.session.notify_agent_run_started()
|
|
141
173
|
self.session.ctx.invoke(
|
|
142
|
-
agents_run_command,
|
|
174
|
+
agents_run_command,
|
|
175
|
+
agent_ref=agent_id,
|
|
176
|
+
input_text=message,
|
|
177
|
+
verbose=self.session.verbose_enabled,
|
|
143
178
|
)
|
|
144
179
|
self.session.last_run_input = message
|
|
145
180
|
except click.ClickException as exc:
|
|
146
181
|
self.console.print(f"[red]{exc}[/red]")
|
|
182
|
+
finally:
|
|
183
|
+
try:
|
|
184
|
+
self.session.notify_agent_run_finished()
|
|
185
|
+
finally:
|
|
186
|
+
if isinstance(ctx_obj, dict):
|
|
187
|
+
if previous_session is None:
|
|
188
|
+
ctx_obj.pop("_slash_session", None)
|
|
189
|
+
else:
|
|
190
|
+
ctx_obj["_slash_session"] = previous_session
|
glaip_sdk/cli/slash/prompt.py
CHANGED
|
@@ -14,20 +14,27 @@ _HAS_PROMPT_TOOLKIT = False
|
|
|
14
14
|
try: # pragma: no cover - optional dependency
|
|
15
15
|
from prompt_toolkit import PromptSession
|
|
16
16
|
from prompt_toolkit.completion import Completer, Completion
|
|
17
|
-
from prompt_toolkit.formatted_text import FormattedText
|
|
17
|
+
from prompt_toolkit.formatted_text import FormattedText, to_formatted_text
|
|
18
18
|
from prompt_toolkit.key_binding import KeyBindings
|
|
19
19
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
20
20
|
from prompt_toolkit.styles import Style
|
|
21
21
|
|
|
22
|
+
try:
|
|
23
|
+
from prompt_toolkit.application import run_in_terminal as ptk_run_in_terminal
|
|
24
|
+
except Exception: # pragma: no cover - compatibility fallback
|
|
25
|
+
ptk_run_in_terminal = None
|
|
26
|
+
|
|
22
27
|
_HAS_PROMPT_TOOLKIT = True
|
|
23
28
|
except Exception: # pragma: no cover - optional dependency
|
|
24
29
|
PromptSession = None # type: ignore[assignment]
|
|
25
30
|
Completer = None # type: ignore[assignment]
|
|
26
31
|
Completion = None # type: ignore[assignment]
|
|
27
32
|
FormattedText = None # type: ignore[assignment]
|
|
33
|
+
to_formatted_text = None # type: ignore[assignment]
|
|
28
34
|
KeyBindings = None # type: ignore[assignment]
|
|
29
35
|
Style = None # type: ignore[assignment]
|
|
30
36
|
patch_stdout = None # type: ignore[assignment]
|
|
37
|
+
ptk_run_in_terminal = None
|
|
31
38
|
|
|
32
39
|
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
33
40
|
from .session import SlashSession
|
|
@@ -76,7 +83,7 @@ def setup_prompt_toolkit(
|
|
|
76
83
|
if PromptSession is None or Style is None:
|
|
77
84
|
return None, None
|
|
78
85
|
|
|
79
|
-
bindings = _create_key_bindings()
|
|
86
|
+
bindings = _create_key_bindings(session)
|
|
80
87
|
|
|
81
88
|
prompt_session = PromptSession(
|
|
82
89
|
completer=SlashCompleter(session),
|
|
@@ -86,6 +93,8 @@ def setup_prompt_toolkit(
|
|
|
86
93
|
prompt_style = Style.from_dict(
|
|
87
94
|
{
|
|
88
95
|
"prompt": "bg:#0f172a #facc15 bold",
|
|
96
|
+
"prompt-verbose-on": "bg:#0f172a #34d399 bold",
|
|
97
|
+
"prompt-verbose-off": "bg:#0f172a #f87171 bold",
|
|
89
98
|
"": "bg:#0f172a #e2e8f0",
|
|
90
99
|
"placeholder": "bg:#0f172a #94a3b8 italic",
|
|
91
100
|
}
|
|
@@ -94,7 +103,7 @@ def setup_prompt_toolkit(
|
|
|
94
103
|
return prompt_session, prompt_style
|
|
95
104
|
|
|
96
105
|
|
|
97
|
-
def _create_key_bindings() -> Any:
|
|
106
|
+
def _create_key_bindings(session: SlashSession) -> Any:
|
|
98
107
|
"""Create prompt_toolkit key bindings for the command palette."""
|
|
99
108
|
|
|
100
109
|
if KeyBindings is None:
|
|
@@ -109,29 +118,57 @@ def _create_key_bindings() -> Any:
|
|
|
109
118
|
elif buffer.complete_state is not None:
|
|
110
119
|
buffer.cancel_completion()
|
|
111
120
|
|
|
112
|
-
@bindings.add("/") # type: ignore[misc]
|
|
113
121
|
def _trigger_slash_completion(event: Any) -> None: # pragma: no cover - UI
|
|
114
122
|
buffer = event.app.current_buffer
|
|
115
123
|
buffer.insert_text("/")
|
|
116
124
|
_refresh_completions(buffer)
|
|
117
125
|
|
|
118
|
-
@bindings.add("backspace") # type: ignore[misc]
|
|
119
126
|
def _handle_backspace(event: Any) -> None: # pragma: no cover - UI
|
|
120
127
|
buffer = event.app.current_buffer
|
|
121
128
|
if buffer.document.cursor_position > 0:
|
|
122
129
|
buffer.delete_before_cursor()
|
|
123
130
|
_refresh_completions(buffer)
|
|
124
131
|
|
|
132
|
+
def _toggle_verbose(event: Any) -> None: # pragma: no cover - UI
|
|
133
|
+
_execute_toggle_verbose(session, event.app)
|
|
134
|
+
|
|
135
|
+
@bindings.add("/") # type: ignore[misc]
|
|
136
|
+
def _add_trigger_slash_completion(event: Any) -> None:
|
|
137
|
+
_trigger_slash_completion(event)
|
|
138
|
+
|
|
139
|
+
@bindings.add("backspace") # type: ignore[misc]
|
|
140
|
+
def _add_handle_backspace(event: Any) -> None:
|
|
141
|
+
_handle_backspace(event)
|
|
142
|
+
|
|
125
143
|
@bindings.add("c-h") # type: ignore[misc]
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
def _add_handle_ctrl_h(event: Any) -> None:
|
|
145
|
+
_handle_backspace(event) # Reuse backspace handler
|
|
146
|
+
|
|
147
|
+
@bindings.add("c-t") # type: ignore[misc]
|
|
148
|
+
def _add_toggle_verbose(event: Any) -> None:
|
|
149
|
+
_toggle_verbose(event)
|
|
131
150
|
|
|
132
151
|
return bindings
|
|
133
152
|
|
|
134
153
|
|
|
154
|
+
def _execute_toggle_verbose(
|
|
155
|
+
session: SlashSession, app: Any
|
|
156
|
+
) -> None: # pragma: no cover - UI
|
|
157
|
+
"""Execute verbose toggle with proper terminal handling."""
|
|
158
|
+
|
|
159
|
+
def _announce() -> None:
|
|
160
|
+
session.toggle_verbose(announce=False)
|
|
161
|
+
|
|
162
|
+
run_in_terminal = getattr(app, "run_in_terminal", None)
|
|
163
|
+
if callable(run_in_terminal):
|
|
164
|
+
run_in_terminal(_announce)
|
|
165
|
+
elif ptk_run_in_terminal is not None:
|
|
166
|
+
ptk_run_in_terminal(_announce)
|
|
167
|
+
else:
|
|
168
|
+
_announce()
|
|
169
|
+
app.invalidate()
|
|
170
|
+
|
|
171
|
+
|
|
135
172
|
def _iter_command_completions(
|
|
136
173
|
session: SlashSession, text: str
|
|
137
174
|
) -> Iterable[Completion]: # pragma: no cover - thin wrapper
|
|
@@ -191,6 +228,7 @@ __all__ = [
|
|
|
191
228
|
"SlashCompleter",
|
|
192
229
|
"setup_prompt_toolkit",
|
|
193
230
|
"FormattedText",
|
|
231
|
+
"to_formatted_text",
|
|
194
232
|
"patch_stdout",
|
|
195
233
|
"PromptSession",
|
|
196
234
|
"Style",
|