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.
- glaip_sdk/__init__.py +1 -1
- glaip_sdk/branding.py +28 -2
- glaip_sdk/cli/commands/agents.py +36 -27
- glaip_sdk/cli/commands/configure.py +46 -52
- glaip_sdk/cli/commands/mcps.py +19 -22
- glaip_sdk/cli/commands/tools.py +19 -13
- glaip_sdk/cli/config.py +42 -0
- glaip_sdk/cli/display.py +97 -30
- glaip_sdk/cli/main.py +141 -124
- glaip_sdk/cli/mcp_validators.py +2 -2
- glaip_sdk/cli/pager.py +3 -2
- glaip_sdk/cli/parsers/json_input.py +2 -2
- glaip_sdk/cli/resolution.py +12 -10
- glaip_sdk/cli/rich_helpers.py +29 -0
- 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 +115 -58
- glaip_sdk/client/_agent_payloads.py +504 -0
- glaip_sdk/client/agents.py +633 -559
- glaip_sdk/client/base.py +92 -20
- glaip_sdk/client/main.py +14 -0
- glaip_sdk/client/run_rendering.py +275 -0
- glaip_sdk/config/constants.py +4 -1
- 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.15.dist-info → glaip_sdk-0.0.17.dist-info}/METADATA +2 -2
- glaip_sdk-0.0.17.dist-info/RECORD +73 -0
- glaip_sdk-0.0.15.dist-info/RECORD +0 -67
- {glaip_sdk-0.0.15.dist-info → glaip_sdk-0.0.17.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.15.dist-info → glaip_sdk-0.0.17.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/tools.py
CHANGED
|
@@ -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.io import (
|
|
|
33
32
|
load_resource_from_file_with_validation as load_resource_from_file,
|
|
34
33
|
)
|
|
35
34
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
35
|
+
from glaip_sdk.cli.rich_helpers import markup_text, print_markup
|
|
36
36
|
from glaip_sdk.cli.utils import (
|
|
37
37
|
coerce_to_row,
|
|
38
38
|
get_client,
|
|
@@ -349,11 +349,13 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
|
|
|
349
349
|
):
|
|
350
350
|
tool = client.get_tool_by_id(tool.id)
|
|
351
351
|
except Exception as e:
|
|
352
|
-
|
|
353
|
-
|
|
352
|
+
print_markup(
|
|
353
|
+
f"[yellow]⚠️ Could not fetch full tool details: {e}[/yellow]",
|
|
354
|
+
console=console,
|
|
354
355
|
)
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
print_markup(
|
|
357
|
+
"[yellow]⚠️ Proceeding with available data[/yellow]",
|
|
358
|
+
console=console,
|
|
357
359
|
)
|
|
358
360
|
|
|
359
361
|
with spinner_context(
|
|
@@ -362,10 +364,9 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
|
|
|
362
364
|
console_override=console,
|
|
363
365
|
):
|
|
364
366
|
export_resource_to_file(tool, export_path, detected_format)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
)
|
|
367
|
+
print_markup(
|
|
368
|
+
f"[green]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/green]",
|
|
369
|
+
console=console,
|
|
369
370
|
)
|
|
370
371
|
|
|
371
372
|
# Try to fetch raw API data first to preserve ALL fields
|
|
@@ -473,7 +474,8 @@ def update(
|
|
|
473
474
|
tool.id, file, framework=tool.framework
|
|
474
475
|
)
|
|
475
476
|
handle_rich_output(
|
|
476
|
-
ctx,
|
|
477
|
+
ctx,
|
|
478
|
+
markup_text(f"[green]✓[/green] Tool code updated from {file}"),
|
|
477
479
|
)
|
|
478
480
|
elif update_data:
|
|
479
481
|
# Update metadata only (native tools only)
|
|
@@ -487,9 +489,13 @@ def update(
|
|
|
487
489
|
console_override=console,
|
|
488
490
|
):
|
|
489
491
|
updated_tool = tool.update(**update_data)
|
|
490
|
-
handle_rich_output(
|
|
492
|
+
handle_rich_output(
|
|
493
|
+
ctx, markup_text("[green]✓[/green] Tool metadata updated")
|
|
494
|
+
)
|
|
491
495
|
else:
|
|
492
|
-
handle_rich_output(
|
|
496
|
+
handle_rich_output(
|
|
497
|
+
ctx, markup_text("[yellow]No updates specified[/yellow]")
|
|
498
|
+
)
|
|
493
499
|
return
|
|
494
500
|
|
|
495
501
|
handle_json_output(ctx, updated_tool.model_dump())
|
|
@@ -574,5 +580,5 @@ def script(ctx: Any, tool_id: str) -> None:
|
|
|
574
580
|
except Exception as e:
|
|
575
581
|
handle_json_output(ctx, error=e)
|
|
576
582
|
if get_ctx_value(ctx, "view") != "json":
|
|
577
|
-
|
|
583
|
+
print_markup(f"[red]Error getting tool script: {e}[/red]", console=console)
|
|
578
584
|
raise click.ClickException(str(e))
|
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/display.py
CHANGED
|
@@ -15,6 +15,8 @@ from rich.console import Console
|
|
|
15
15
|
from rich.panel import Panel
|
|
16
16
|
from rich.text import Text
|
|
17
17
|
|
|
18
|
+
from glaip_sdk.cli.rich_helpers import markup_text, print_markup
|
|
19
|
+
from glaip_sdk.cli.utils import command_hint
|
|
18
20
|
from glaip_sdk.rich_components import AIPPanel
|
|
19
21
|
|
|
20
22
|
console = Console()
|
|
@@ -60,7 +62,7 @@ def display_update_success(resource_type: str, resource_name: str) -> Text:
|
|
|
60
62
|
Returns:
|
|
61
63
|
Rich Text object for display
|
|
62
64
|
"""
|
|
63
|
-
return
|
|
65
|
+
return markup_text(
|
|
64
66
|
f"[green]✅ {resource_type} '{resource_name}' updated successfully[/green]"
|
|
65
67
|
)
|
|
66
68
|
|
|
@@ -75,7 +77,7 @@ def display_deletion_success(resource_type: str, resource_name: str) -> Text:
|
|
|
75
77
|
Returns:
|
|
76
78
|
Rich Text object for display
|
|
77
79
|
"""
|
|
78
|
-
return
|
|
80
|
+
return markup_text(
|
|
79
81
|
f"[green]✅ {resource_type} '{resource_name}' deleted successfully[/green]"
|
|
80
82
|
)
|
|
81
83
|
|
|
@@ -88,8 +90,8 @@ def display_api_error(error: Exception, operation: str = "operation") -> None:
|
|
|
88
90
|
operation: Description of the operation that failed
|
|
89
91
|
"""
|
|
90
92
|
error_type = type(error).__name__
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
print_markup(f"[red]Error during {operation}: {error}[/red]", console=console)
|
|
94
|
+
print_markup(f"[dim]Error type: {error_type}[/dim]", console=console)
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
def print_api_error(e: Exception) -> None:
|
|
@@ -102,30 +104,80 @@ def print_api_error(e: Exception) -> None:
|
|
|
102
104
|
- Extracts status_code, error_type, and payload from APIError exceptions
|
|
103
105
|
- Provides consistent error reporting across CLI commands
|
|
104
106
|
- Handles both JSON and Rich output formats
|
|
107
|
+
- Special handling for validation errors with detailed field-level errors
|
|
105
108
|
"""
|
|
106
|
-
if hasattr(e, "__dict__"):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
109
|
+
if not hasattr(e, "__dict__"):
|
|
110
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if not hasattr(e, "status_code"):
|
|
114
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
console.print(f"[red]API Error: {e}[/red]")
|
|
118
|
+
status_code = getattr(e, "status_code", None)
|
|
119
|
+
if status_code is not None:
|
|
120
|
+
console.print(f"[yellow]Status: {status_code}[/yellow]")
|
|
121
|
+
|
|
122
|
+
payload = getattr(e, "payload", _MISSING)
|
|
123
|
+
if payload is _MISSING:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
if payload:
|
|
127
|
+
if not _print_structured_payload(payload):
|
|
128
|
+
console.print(f"[yellow]Details: {payload}[/yellow]")
|
|
129
|
+
else:
|
|
130
|
+
console.print(f"[yellow]Details: {payload}[/yellow]")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _print_structured_payload(payload: Any) -> bool:
|
|
134
|
+
"""Print structured payloads with enhanced formatting. Returns True if handled."""
|
|
135
|
+
if not isinstance(payload, dict):
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
if "detail" in payload and _print_validation_details(payload["detail"]):
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
if "details" in payload and _print_details_field(payload["details"]):
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _print_validation_details(detail: Any) -> bool:
|
|
148
|
+
"""Render FastAPI-style validation errors."""
|
|
149
|
+
if not isinstance(detail, list) or not detail:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
console.print("[red]Validation Errors:[/red]")
|
|
153
|
+
for error in detail:
|
|
154
|
+
if isinstance(error, dict):
|
|
155
|
+
loc = " -> ".join(str(x) for x in error.get("loc", []))
|
|
156
|
+
msg = error.get("msg", "Unknown error")
|
|
157
|
+
error_type = error.get("type", "unknown")
|
|
158
|
+
location = loc if loc else "field"
|
|
159
|
+
console.print(f" [yellow]• {location}:[/yellow] {msg}")
|
|
160
|
+
if error_type != "unknown":
|
|
161
|
+
console.print(f" [dim]({error_type})[/dim]")
|
|
125
162
|
else:
|
|
126
|
-
console.print(f"[
|
|
163
|
+
console.print(f" [yellow]•[/yellow] {error}")
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _print_details_field(details: Any) -> bool:
|
|
168
|
+
"""Render custom error details from API payloads."""
|
|
169
|
+
if not details:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
console.print("[red]Error Details:[/red]")
|
|
173
|
+
if isinstance(details, str):
|
|
174
|
+
console.print(f" [yellow]•[/yellow] {details}")
|
|
175
|
+
elif isinstance(details, list):
|
|
176
|
+
for detail in details:
|
|
177
|
+
console.print(f" [yellow]•[/yellow] {detail}")
|
|
127
178
|
else:
|
|
128
|
-
console.print(f"[
|
|
179
|
+
console.print(f" [yellow]•[/yellow] {details}")
|
|
180
|
+
return True
|
|
129
181
|
|
|
130
182
|
|
|
131
183
|
_MISSING = object()
|
|
@@ -133,7 +185,6 @@ _MISSING = object()
|
|
|
133
185
|
|
|
134
186
|
def build_resource_result_data(resource: Any, fields: list[str]) -> dict[str, Any]:
|
|
135
187
|
"""Return a normalized mapping of ``fields`` extracted from ``resource``."""
|
|
136
|
-
|
|
137
188
|
result: dict[str, Any] = {}
|
|
138
189
|
for field in fields:
|
|
139
190
|
try:
|
|
@@ -244,14 +295,30 @@ def display_confirmation_prompt(resource_type: str, resource_name: str) -> bool:
|
|
|
244
295
|
|
|
245
296
|
def display_agent_run_suggestions(agent: Any) -> Panel:
|
|
246
297
|
"""Return a panel with post-creation suggestions for an agent."""
|
|
298
|
+
agent_id = getattr(agent, "id", "")
|
|
299
|
+
agent_name = getattr(agent, "name", "")
|
|
300
|
+
run_hint_id = command_hint(
|
|
301
|
+
f'agents run {agent_id} "Your message here"',
|
|
302
|
+
slash_command=None,
|
|
303
|
+
)
|
|
304
|
+
run_hint_name = command_hint(
|
|
305
|
+
f'agents run "{agent_name}" "Your message here"',
|
|
306
|
+
slash_command=None,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
cli_section = ""
|
|
310
|
+
if run_hint_id and run_hint_name:
|
|
311
|
+
cli_section = (
|
|
312
|
+
"📋 Prefer the CLI instead?\n"
|
|
313
|
+
f" [green]{run_hint_id}[/green]\n"
|
|
314
|
+
f" [green]{run_hint_name}[/green]\n\n"
|
|
315
|
+
)
|
|
247
316
|
|
|
248
317
|
return AIPPanel(
|
|
249
318
|
f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
|
|
250
|
-
f"🚀 Start chatting with [bold]{
|
|
319
|
+
f"🚀 Start chatting with [bold]{agent_name}[/bold] right here:\n"
|
|
251
320
|
f" Type your message below and press Enter to run it immediately.\n\n"
|
|
252
|
-
f"
|
|
253
|
-
f' [green]aip agents run {agent.id} "Your message here"[/green]\n'
|
|
254
|
-
f' [green]aip agents run "{agent.name}" "Your message here"[/green]\n\n'
|
|
321
|
+
f"{cli_section}"
|
|
255
322
|
f"🔧 Available options:\n"
|
|
256
323
|
f" [dim]--chat-history[/dim] Include previous conversation\n"
|
|
257
324
|
f" [dim]--file[/dim] Attach files\n"
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -19,11 +19,11 @@ from glaip_sdk.cli.commands.agents import agents_group
|
|
|
19
19
|
from glaip_sdk.cli.commands.configure import (
|
|
20
20
|
config_group,
|
|
21
21
|
configure_command,
|
|
22
|
-
load_config,
|
|
23
22
|
)
|
|
24
23
|
from glaip_sdk.cli.commands.mcps import mcps_group
|
|
25
24
|
from glaip_sdk.cli.commands.models import models_group
|
|
26
25
|
from glaip_sdk.cli.commands.tools import tools_group
|
|
26
|
+
from glaip_sdk.cli.config import load_config
|
|
27
27
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
28
28
|
from glaip_sdk.cli.utils import spinner_context, update_spinner
|
|
29
29
|
from glaip_sdk.config.constants import (
|
|
@@ -73,7 +73,6 @@ def main(
|
|
|
73
73
|
aip tools create my_tool.py # Create a new tool
|
|
74
74
|
aip agents run my-agent "Hello world" # Run an agent
|
|
75
75
|
"""
|
|
76
|
-
|
|
77
76
|
# Store configuration in context
|
|
78
77
|
ctx.ensure_object(dict)
|
|
79
78
|
ctx.obj["api_url"] = api_url
|
|
@@ -116,7 +115,6 @@ main.add_command(configure_command)
|
|
|
116
115
|
|
|
117
116
|
def _should_launch_slash(ctx: click.Context) -> bool:
|
|
118
117
|
"""Determine whether to open the command palette automatically."""
|
|
119
|
-
|
|
120
118
|
ctx_obj = ctx.obj or {}
|
|
121
119
|
if not bool(ctx_obj.get("tty", True)):
|
|
122
120
|
return False
|
|
@@ -127,6 +125,137 @@ def _should_launch_slash(ctx: click.Context) -> bool:
|
|
|
127
125
|
return True
|
|
128
126
|
|
|
129
127
|
|
|
128
|
+
def _load_and_merge_config(ctx: click.Context) -> dict:
|
|
129
|
+
"""Load configuration from multiple sources and merge them."""
|
|
130
|
+
# Load config from file and merge with context
|
|
131
|
+
file_config = load_config()
|
|
132
|
+
context_config = ctx.obj or {}
|
|
133
|
+
|
|
134
|
+
# Load environment variables (middle priority)
|
|
135
|
+
env_config = {}
|
|
136
|
+
if os.getenv("AIP_API_URL"):
|
|
137
|
+
env_config["api_url"] = os.getenv("AIP_API_URL")
|
|
138
|
+
if os.getenv("AIP_API_KEY"):
|
|
139
|
+
env_config["api_key"] = os.getenv("AIP_API_KEY")
|
|
140
|
+
|
|
141
|
+
# Filter out None values from context config to avoid overriding other configs
|
|
142
|
+
filtered_context = {k: v for k, v in context_config.items() if v is not None}
|
|
143
|
+
|
|
144
|
+
# Merge configs: file (low) -> env (mid) -> CLI args (high)
|
|
145
|
+
return {**file_config, **env_config, **filtered_context}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _validate_config_and_show_error(config: dict, console: Console) -> None:
|
|
149
|
+
"""Validate configuration and show error if incomplete."""
|
|
150
|
+
if not config.get("api_url") or not config.get("api_key"):
|
|
151
|
+
console.print(
|
|
152
|
+
AIPPanel(
|
|
153
|
+
"[bold red]❌ Configuration incomplete[/bold red]\n\n"
|
|
154
|
+
f"🔍 Current config:\n"
|
|
155
|
+
f" • API URL: {config.get('api_url', 'Not set')}\n"
|
|
156
|
+
f" • API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
|
|
157
|
+
f"💡 To fix this:\n"
|
|
158
|
+
f" • Run 'aip configure' to set up credentials\n"
|
|
159
|
+
f" • Or run 'aip config list' to see current config",
|
|
160
|
+
title="❌ Configuration Error",
|
|
161
|
+
border_style="red",
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
console.print(
|
|
165
|
+
f"\n[bold green]✅ AIP - Ready[/bold green] (SDK v{_SDK_VERSION}) - Configure to connect"
|
|
166
|
+
)
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _create_and_test_client(config: dict, console: Console) -> Client:
|
|
171
|
+
"""Create client and test connection by fetching resources."""
|
|
172
|
+
# Try to create client
|
|
173
|
+
client = Client(
|
|
174
|
+
api_url=config["api_url"],
|
|
175
|
+
api_key=config["api_key"],
|
|
176
|
+
timeout=config.get("timeout", 30.0),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Test connection by listing resources
|
|
180
|
+
try:
|
|
181
|
+
with spinner_context(
|
|
182
|
+
None, # We'll pass ctx later
|
|
183
|
+
"[bold blue]Checking GL AIP status…[/bold blue]",
|
|
184
|
+
console_override=console,
|
|
185
|
+
spinner_style="cyan",
|
|
186
|
+
) as status_indicator:
|
|
187
|
+
update_spinner(status_indicator, "[bold blue]Fetching agents…[/bold blue]")
|
|
188
|
+
agents = client.list_agents()
|
|
189
|
+
|
|
190
|
+
update_spinner(status_indicator, "[bold blue]Fetching tools…[/bold blue]")
|
|
191
|
+
tools = client.list_tools()
|
|
192
|
+
|
|
193
|
+
update_spinner(status_indicator, "[bold blue]Fetching MCPs…[/bold blue]")
|
|
194
|
+
mcps = client.list_mcps()
|
|
195
|
+
|
|
196
|
+
# Create status table
|
|
197
|
+
table = AIPTable(title="🔗 GL AIP Status")
|
|
198
|
+
table.add_column("Resource", style="cyan", width=15)
|
|
199
|
+
table.add_column("Count", style="green", width=10)
|
|
200
|
+
table.add_column("Status", style="green", width=15)
|
|
201
|
+
|
|
202
|
+
table.add_row("Agents", str(len(agents)), "✅ Available")
|
|
203
|
+
table.add_row("Tools", str(len(tools)), "✅ Available")
|
|
204
|
+
table.add_row("MCPs", str(len(mcps)), "✅ Available")
|
|
205
|
+
|
|
206
|
+
console.print(
|
|
207
|
+
AIPPanel(
|
|
208
|
+
f"[bold green]✅ Connected to GL AIP[/bold green]\n"
|
|
209
|
+
f"🔗 API URL: {client.api_url}\n"
|
|
210
|
+
f"🤖 Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
|
|
211
|
+
title="🚀 Connection Status",
|
|
212
|
+
border_style="green",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
console.print(table)
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
# Show AIP Ready status even if connection fails
|
|
220
|
+
console.print(
|
|
221
|
+
f"\n[bold green]✅ AIP - Ready[/bold green] (SDK v{_SDK_VERSION})"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
console.print(
|
|
225
|
+
AIPPanel(
|
|
226
|
+
f"[bold yellow]⚠️ Connection established but API call failed[/bold yellow]\n"
|
|
227
|
+
f"🔗 API URL: {client.api_url}\n"
|
|
228
|
+
f"❌ Error: {e}\n\n"
|
|
229
|
+
f"💡 This usually means:\n"
|
|
230
|
+
f" • Network connectivity issues\n"
|
|
231
|
+
f" • API permissions problems\n"
|
|
232
|
+
f" • Backend service issues",
|
|
233
|
+
title="⚠️ Partial Connection",
|
|
234
|
+
border_style="yellow",
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return client
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _handle_connection_error(config: dict, console: Console, error: Exception) -> None:
|
|
242
|
+
"""Handle connection errors and show troubleshooting information."""
|
|
243
|
+
console.print(
|
|
244
|
+
AIPPanel(
|
|
245
|
+
f"[bold red]❌ Connection failed[/bold red]\n\n"
|
|
246
|
+
f"🔍 Error: {error}\n\n"
|
|
247
|
+
f"💡 Troubleshooting steps:\n"
|
|
248
|
+
f" • Verify your API URL and key are correct\n"
|
|
249
|
+
f" • Check network connectivity to {config.get('api_url', 'your API')}\n"
|
|
250
|
+
f" • Run 'aip configure' to update credentials\n"
|
|
251
|
+
f" • Run 'aip config list' to check configuration",
|
|
252
|
+
title="❌ Connection Error",
|
|
253
|
+
border_style="red",
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
|
|
258
|
+
|
|
130
259
|
@main.command()
|
|
131
260
|
@click.pass_context
|
|
132
261
|
def status(ctx: Any) -> None:
|
|
@@ -146,132 +275,20 @@ def status(ctx: Any) -> None:
|
|
|
146
275
|
f"\n[bold green]✅ AIP - Ready[/bold green] (SDK v{_SDK_VERSION})"
|
|
147
276
|
)
|
|
148
277
|
|
|
149
|
-
# Load
|
|
150
|
-
|
|
151
|
-
context_config = ctx.obj or {}
|
|
152
|
-
|
|
153
|
-
# Load environment variables (middle priority)
|
|
154
|
-
|
|
155
|
-
env_config = {}
|
|
156
|
-
if os.getenv("AIP_API_URL"):
|
|
157
|
-
env_config["api_url"] = os.getenv("AIP_API_URL")
|
|
158
|
-
if os.getenv("AIP_API_KEY"):
|
|
159
|
-
env_config["api_key"] = os.getenv("AIP_API_KEY")
|
|
160
|
-
|
|
161
|
-
# Filter out None values from context config to avoid overriding other configs
|
|
162
|
-
filtered_context = {k: v for k, v in context_config.items() if v is not None}
|
|
163
|
-
|
|
164
|
-
# Merge configs: file (low) -> env (mid) -> CLI args (high)
|
|
165
|
-
config = {**file_config, **env_config, **filtered_context}
|
|
166
|
-
|
|
167
|
-
if not config.get("api_url") or not config.get("api_key"):
|
|
168
|
-
console.print(
|
|
169
|
-
AIPPanel(
|
|
170
|
-
"[bold red]❌ Configuration incomplete[/bold red]\n\n"
|
|
171
|
-
f"🔍 Current config:\n"
|
|
172
|
-
f" • API URL: {config.get('api_url', 'Not set')}\n"
|
|
173
|
-
f" • API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
|
|
174
|
-
f"💡 To fix this:\n"
|
|
175
|
-
f" • Run 'aip configure' to set up credentials\n"
|
|
176
|
-
f" • Or run 'aip config list' to see current config",
|
|
177
|
-
title="❌ Configuration Error",
|
|
178
|
-
border_style="red",
|
|
179
|
-
)
|
|
180
|
-
)
|
|
181
|
-
console.print(
|
|
182
|
-
f"\n[bold green]✅ AIP - Ready[/bold green] (SDK v{_SDK_VERSION}) - Configure to connect"
|
|
183
|
-
)
|
|
184
|
-
sys.exit(1)
|
|
185
|
-
|
|
186
|
-
# Try to create client
|
|
187
|
-
client = Client(
|
|
188
|
-
api_url=config["api_url"],
|
|
189
|
-
api_key=config["api_key"],
|
|
190
|
-
timeout=config.get("timeout", 30.0),
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
# Test connection by listing resources
|
|
194
|
-
try:
|
|
195
|
-
with spinner_context(
|
|
196
|
-
ctx,
|
|
197
|
-
"[bold blue]Checking GL AIP status…[/bold blue]",
|
|
198
|
-
console_override=console,
|
|
199
|
-
spinner_style="cyan",
|
|
200
|
-
) as status_indicator:
|
|
201
|
-
update_spinner(
|
|
202
|
-
status_indicator, "[bold blue]Fetching agents…[/bold blue]"
|
|
203
|
-
)
|
|
204
|
-
agents = client.list_agents()
|
|
278
|
+
# Load and merge configuration
|
|
279
|
+
config = _load_and_merge_config(ctx)
|
|
205
280
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
209
|
-
tools = client.list_tools()
|
|
210
|
-
|
|
211
|
-
update_spinner(
|
|
212
|
-
status_indicator, "[bold blue]Fetching MCPs…[/bold blue]"
|
|
213
|
-
)
|
|
214
|
-
mcps = client.list_mcps()
|
|
215
|
-
|
|
216
|
-
# Create status table
|
|
217
|
-
table = AIPTable(title="🔗 GL AIP Status")
|
|
218
|
-
table.add_column("Resource", style="cyan", width=15)
|
|
219
|
-
table.add_column("Count", style="green", width=10)
|
|
220
|
-
table.add_column("Status", style="green", width=15)
|
|
221
|
-
|
|
222
|
-
table.add_row("Agents", str(len(agents)), "✅ Available")
|
|
223
|
-
table.add_row("Tools", str(len(tools)), "✅ Available")
|
|
224
|
-
table.add_row("MCPs", str(len(mcps)), "✅ Available")
|
|
225
|
-
|
|
226
|
-
console.print(
|
|
227
|
-
AIPPanel(
|
|
228
|
-
f"[bold green]✅ Connected to GL AIP[/bold green]\n"
|
|
229
|
-
f"🔗 API URL: {client.api_url}\n"
|
|
230
|
-
f"🤖 Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
|
|
231
|
-
title="🚀 Connection Status",
|
|
232
|
-
border_style="green",
|
|
233
|
-
)
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
console.print(table)
|
|
237
|
-
|
|
238
|
-
except Exception as e:
|
|
239
|
-
# Show AIP Ready status even if connection fails
|
|
240
|
-
console.print(
|
|
241
|
-
f"\n[bold green]✅ AIP - Ready[/bold green] (SDK v{_SDK_VERSION})"
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
console.print(
|
|
245
|
-
AIPPanel(
|
|
246
|
-
f"[bold yellow]⚠️ Connection established but API call failed[/bold yellow]\n"
|
|
247
|
-
f"🔗 API URL: {client.api_url}\n"
|
|
248
|
-
f"❌ Error: {e}\n\n"
|
|
249
|
-
f"💡 This usually means:\n"
|
|
250
|
-
f" • Network connectivity issues\n"
|
|
251
|
-
f" • API permissions problems\n"
|
|
252
|
-
f" • Backend service issues",
|
|
253
|
-
title="⚠️ Partial Connection",
|
|
254
|
-
border_style="yellow",
|
|
255
|
-
)
|
|
256
|
-
)
|
|
281
|
+
# Validate configuration
|
|
282
|
+
_validate_config_and_show_error(config, console)
|
|
257
283
|
|
|
284
|
+
# Create and test client connection
|
|
285
|
+
client = _create_and_test_client(config, console)
|
|
258
286
|
client.close()
|
|
259
287
|
|
|
260
288
|
except Exception as e:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
f"🔍 Error: {e}\n\n"
|
|
265
|
-
f"💡 Troubleshooting steps:\n"
|
|
266
|
-
f" • Run 'aip config list' to check configuration\n"
|
|
267
|
-
f" • Run 'aip configure' to update credentials\n"
|
|
268
|
-
f" • Verify your API URL and key are correct\n"
|
|
269
|
-
f" • Check network connectivity to {config.get('api_url', 'your API')}",
|
|
270
|
-
title="❌ Connection Error",
|
|
271
|
-
border_style="red",
|
|
272
|
-
)
|
|
273
|
-
)
|
|
274
|
-
sys.exit(1)
|
|
289
|
+
# Handle any unexpected errors during the process
|
|
290
|
+
console = Console()
|
|
291
|
+
_handle_connection_error(config if "config" in locals() else {}, console, e)
|
|
275
292
|
|
|
276
293
|
|
|
277
294
|
@main.command()
|
glaip_sdk/cli/mcp_validators.py
CHANGED
|
@@ -15,7 +15,7 @@ import click
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def format_validation_error(prefix: str, detail: str | None = None) -> str:
|
|
18
|
-
"""Format a validation error message with optional detail.
|
|
18
|
+
r"""Format a validation error message with optional detail.
|
|
19
19
|
|
|
20
20
|
Args:
|
|
21
21
|
prefix: Main error message
|
|
@@ -26,7 +26,7 @@ def format_validation_error(prefix: str, detail: str | None = None) -> str:
|
|
|
26
26
|
|
|
27
27
|
Examples:
|
|
28
28
|
>>> format_validation_error("Invalid config", "Missing 'url' field")
|
|
29
|
-
"Invalid config
|
|
29
|
+
"Invalid config\nMissing 'url' field"
|
|
30
30
|
"""
|
|
31
31
|
parts = [prefix]
|
|
32
32
|
if detail:
|
glaip_sdk/cli/pager.py
CHANGED
|
@@ -57,8 +57,9 @@ def _get_console() -> Console:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _prepare_pager_env(clear_on_exit: bool = True) -> None:
|
|
60
|
-
"""
|
|
61
|
-
|
|
60
|
+
"""Configure LESS flags for a predictable, high-quality UX.
|
|
61
|
+
|
|
62
|
+
Sets sensible defaults for the system pager:
|
|
62
63
|
-R : pass ANSI color escapes
|
|
63
64
|
-S : chop long lines (horizontal scroll with ←/→)
|
|
64
65
|
(No -F, no -X) so we open a full-screen pager and clear on exit.
|
|
@@ -17,7 +17,7 @@ import click
|
|
|
17
17
|
def _format_file_error(
|
|
18
18
|
prefix: str, file_path_str: str, resolved_path: Path, *, detail: str | None = None
|
|
19
19
|
) -> str:
|
|
20
|
-
"""Format a file-related error message with path context.
|
|
20
|
+
r"""Format a file-related error message with path context.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
23
|
prefix: Main error message
|
|
@@ -31,7 +31,7 @@ def _format_file_error(
|
|
|
31
31
|
Examples:
|
|
32
32
|
>>> from pathlib import Path
|
|
33
33
|
>>> _format_file_error("File not found", "config.json", Path("/abs/config.json"))
|
|
34
|
-
'File not found: config.json
|
|
34
|
+
'File not found: config.json\nResolved path: /abs/config.json'
|
|
35
35
|
"""
|
|
36
36
|
parts = [f"{prefix}: {file_path_str}", f"Resolved path: {resolved_path}"]
|
|
37
37
|
if detail:
|