glaip-sdk 0.0.18__py3-none-any.whl → 0.0.20__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/_version.py +2 -2
- glaip_sdk/branding.py +27 -2
- glaip_sdk/cli/auth.py +93 -28
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/agents.py +108 -21
- glaip_sdk/cli/commands/configure.py +141 -90
- glaip_sdk/cli/commands/mcps.py +371 -48
- glaip_sdk/cli/commands/models.py +4 -3
- glaip_sdk/cli/commands/tools.py +27 -14
- glaip_sdk/cli/commands/update.py +66 -0
- glaip_sdk/cli/config.py +13 -2
- glaip_sdk/cli/display.py +35 -26
- glaip_sdk/cli/io.py +14 -5
- glaip_sdk/cli/main.py +185 -73
- glaip_sdk/cli/pager.py +2 -1
- glaip_sdk/cli/parsers/json_input.py +62 -14
- glaip_sdk/cli/resolution.py +4 -1
- glaip_sdk/cli/slash/__init__.py +3 -4
- glaip_sdk/cli/slash/agent_session.py +88 -36
- glaip_sdk/cli/slash/prompt.py +20 -48
- glaip_sdk/cli/slash/session.py +440 -189
- glaip_sdk/cli/transcript/__init__.py +71 -0
- glaip_sdk/cli/transcript/cache.py +338 -0
- glaip_sdk/cli/transcript/capture.py +278 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/launcher.py +79 -0
- glaip_sdk/cli/transcript/viewer.py +624 -0
- glaip_sdk/cli/update_notifier.py +29 -5
- glaip_sdk/cli/utils.py +256 -74
- glaip_sdk/client/agents.py +3 -1
- glaip_sdk/client/run_rendering.py +2 -2
- glaip_sdk/icons.py +19 -0
- glaip_sdk/models.py +6 -0
- glaip_sdk/rich_components.py +29 -1
- glaip_sdk/utils/__init__.py +1 -1
- glaip_sdk/utils/client_utils.py +6 -4
- glaip_sdk/utils/display.py +61 -32
- glaip_sdk/utils/rendering/formatting.py +6 -5
- glaip_sdk/utils/rendering/renderer/base.py +213 -66
- glaip_sdk/utils/rendering/renderer/debug.py +73 -16
- glaip_sdk/utils/rendering/renderer/panels.py +27 -15
- glaip_sdk/utils/rendering/renderer/progress.py +61 -38
- glaip_sdk/utils/serialization.py +5 -2
- glaip_sdk/utils/validation.py +1 -2
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.20.dist-info}/METADATA +1 -1
- glaip_sdk-0.0.20.dist-info/RECORD +80 -0
- glaip_sdk/utils/rich_utils.py +0 -29
- glaip_sdk-0.0.18.dist-info/RECORD +0 -73
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.20.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.20.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/main.py
CHANGED
|
@@ -14,7 +14,18 @@ from rich.console import Console
|
|
|
14
14
|
|
|
15
15
|
from glaip_sdk import Client
|
|
16
16
|
from glaip_sdk._version import __version__ as _SDK_VERSION
|
|
17
|
-
from glaip_sdk.branding import
|
|
17
|
+
from glaip_sdk.branding import (
|
|
18
|
+
ERROR,
|
|
19
|
+
ERROR_STYLE,
|
|
20
|
+
INFO,
|
|
21
|
+
INFO_STYLE,
|
|
22
|
+
NEUTRAL,
|
|
23
|
+
SUCCESS,
|
|
24
|
+
SUCCESS_STYLE,
|
|
25
|
+
WARNING,
|
|
26
|
+
WARNING_STYLE,
|
|
27
|
+
AIPBranding,
|
|
28
|
+
)
|
|
18
29
|
from glaip_sdk.cli.commands.agents import agents_group
|
|
19
30
|
from glaip_sdk.cli.commands.configure import (
|
|
20
31
|
config_group,
|
|
@@ -23,12 +34,15 @@ from glaip_sdk.cli.commands.configure import (
|
|
|
23
34
|
from glaip_sdk.cli.commands.mcps import mcps_group
|
|
24
35
|
from glaip_sdk.cli.commands.models import models_group
|
|
25
36
|
from glaip_sdk.cli.commands.tools import tools_group
|
|
37
|
+
from glaip_sdk.cli.commands.update import update_command
|
|
26
38
|
from glaip_sdk.cli.config import load_config
|
|
39
|
+
from glaip_sdk.cli.transcript import get_transcript_cache_stats
|
|
27
40
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
28
|
-
from glaip_sdk.cli.utils import spinner_context, update_spinner
|
|
41
|
+
from glaip_sdk.cli.utils import in_slash_mode, spinner_context, update_spinner
|
|
29
42
|
from glaip_sdk.config.constants import (
|
|
30
43
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
31
44
|
)
|
|
45
|
+
from glaip_sdk.icons import ICON_AGENT
|
|
32
46
|
from glaip_sdk.rich_components import AIPPanel, AIPTable
|
|
33
47
|
|
|
34
48
|
# Import SlashSession for potential mocking in tests
|
|
@@ -38,11 +52,40 @@ except ImportError: # pragma: no cover - optional slash dependencies
|
|
|
38
52
|
# Slash dependencies might not be available in all environments
|
|
39
53
|
SlashSession = None
|
|
40
54
|
|
|
55
|
+
# Constants
|
|
56
|
+
AVAILABLE_STATUS = "✅ Available"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _format_size(num: int) -> str:
|
|
60
|
+
"""Return a human-readable byte size."""
|
|
61
|
+
if num <= 0:
|
|
62
|
+
return "0B"
|
|
63
|
+
|
|
64
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
65
|
+
value = float(num)
|
|
66
|
+
for unit in units:
|
|
67
|
+
if value < 1024 or unit == units[-1]:
|
|
68
|
+
if value >= 100 or unit == "B":
|
|
69
|
+
return f"{value:.0f}{unit}"
|
|
70
|
+
if value >= 10:
|
|
71
|
+
return f"{value:.1f}{unit}"
|
|
72
|
+
return f"{value:.2f}{unit}"
|
|
73
|
+
value /= 1024
|
|
74
|
+
return f"{value:.1f}TB" # pragma: no cover - defensive fallback
|
|
75
|
+
|
|
41
76
|
|
|
42
77
|
@click.group(invoke_without_command=True)
|
|
43
78
|
@click.version_option(version=_SDK_VERSION, prog_name="aip")
|
|
44
|
-
@click.option(
|
|
45
|
-
|
|
79
|
+
@click.option(
|
|
80
|
+
"--api-url",
|
|
81
|
+
envvar="AIP_API_URL",
|
|
82
|
+
help="AIP API URL (primary credential for the CLI)",
|
|
83
|
+
)
|
|
84
|
+
@click.option(
|
|
85
|
+
"--api-key",
|
|
86
|
+
envvar="AIP_API_KEY",
|
|
87
|
+
help="AIP API Key (CLI requires this together with --api-url)",
|
|
88
|
+
)
|
|
46
89
|
@click.option("--timeout", default=30.0, help="Request timeout in seconds")
|
|
47
90
|
@click.option(
|
|
48
91
|
"--view",
|
|
@@ -61,17 +104,18 @@ def main(
|
|
|
61
104
|
view: str | None,
|
|
62
105
|
no_tty: bool,
|
|
63
106
|
) -> None:
|
|
64
|
-
"""GL AIP SDK Command Line Interface.
|
|
107
|
+
r"""GL AIP SDK Command Line Interface.
|
|
65
108
|
|
|
66
109
|
A comprehensive CLI for managing GL AIP resources including
|
|
67
110
|
agents, tools, MCPs, and more.
|
|
68
111
|
|
|
112
|
+
\b
|
|
69
113
|
Examples:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
114
|
+
aip version # Show detailed version info
|
|
115
|
+
aip configure # Configure credentials
|
|
116
|
+
aip agents list # List all agents
|
|
117
|
+
aip tools create my_tool.py # Create a new tool
|
|
118
|
+
aip agents run my-agent "Hello world" # Run an agent
|
|
75
119
|
"""
|
|
76
120
|
# Store configuration in context
|
|
77
121
|
ctx.ensure_object(dict)
|
|
@@ -82,15 +126,24 @@ def main(
|
|
|
82
126
|
|
|
83
127
|
ctx.obj["tty"] = not no_tty
|
|
84
128
|
|
|
85
|
-
|
|
129
|
+
launching_slash = (
|
|
130
|
+
ctx.invoked_subcommand is None
|
|
131
|
+
and not ctx.resilient_parsing
|
|
132
|
+
and _should_launch_slash(ctx)
|
|
133
|
+
and SlashSession is not None
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if not ctx.resilient_parsing and ctx.obj["tty"] and not launching_slash:
|
|
86
137
|
console = Console()
|
|
87
138
|
maybe_notify_update(
|
|
88
139
|
_SDK_VERSION,
|
|
89
140
|
console=console,
|
|
141
|
+
ctx=ctx,
|
|
142
|
+
slash_command="update",
|
|
90
143
|
)
|
|
91
144
|
|
|
92
145
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
93
|
-
if
|
|
146
|
+
if launching_slash:
|
|
94
147
|
session = SlashSession(ctx)
|
|
95
148
|
session.run()
|
|
96
149
|
ctx.exit()
|
|
@@ -108,6 +161,7 @@ main.add_command(models_group)
|
|
|
108
161
|
|
|
109
162
|
# Add top-level commands
|
|
110
163
|
main.add_command(configure_command)
|
|
164
|
+
main.add_command(update_command)
|
|
111
165
|
|
|
112
166
|
|
|
113
167
|
# Tip: `--version` is provided by click.version_option above.
|
|
@@ -150,7 +204,7 @@ def _validate_config_and_show_error(config: dict, console: Console) -> None:
|
|
|
150
204
|
if not config.get("api_url") or not config.get("api_key"):
|
|
151
205
|
console.print(
|
|
152
206
|
AIPPanel(
|
|
153
|
-
"[
|
|
207
|
+
f"[{ERROR_STYLE}]❌ Configuration incomplete[/]\n\n"
|
|
154
208
|
f"🔍 Current config:\n"
|
|
155
209
|
f" • API URL: {config.get('api_url', 'Not set')}\n"
|
|
156
210
|
f" • API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
|
|
@@ -158,16 +212,65 @@ def _validate_config_and_show_error(config: dict, console: Console) -> None:
|
|
|
158
212
|
f" • Run 'aip configure' to set up credentials\n"
|
|
159
213
|
f" • Or run 'aip config list' to see current config",
|
|
160
214
|
title="❌ Configuration Error",
|
|
161
|
-
border_style=
|
|
215
|
+
border_style=ERROR,
|
|
162
216
|
)
|
|
163
217
|
)
|
|
164
218
|
console.print(
|
|
165
|
-
f"\n[
|
|
219
|
+
f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{_SDK_VERSION}) - Configure to connect"
|
|
166
220
|
)
|
|
167
221
|
sys.exit(1)
|
|
168
222
|
|
|
169
223
|
|
|
170
|
-
def
|
|
224
|
+
def _resolve_status_console(ctx: Any) -> tuple[Console, bool]:
|
|
225
|
+
"""Return the console to use and whether we are in slash mode."""
|
|
226
|
+
ctx_obj = ctx.obj if isinstance(ctx.obj, dict) else None
|
|
227
|
+
console_override = ctx_obj.get("_slash_console") if ctx_obj else None
|
|
228
|
+
console = console_override or Console()
|
|
229
|
+
slash_mode = in_slash_mode(ctx)
|
|
230
|
+
return console, slash_mode
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _render_status_heading(console: Console, slash_mode: bool) -> None:
|
|
234
|
+
"""Print the status heading/banner."""
|
|
235
|
+
del slash_mode # heading now consistent across invocation contexts
|
|
236
|
+
console.print(f"[{INFO_STYLE}]GL AIP status[/]")
|
|
237
|
+
console.print()
|
|
238
|
+
console.print(f"[{SUCCESS_STYLE}]✅ GL AIP ready[/] (SDK v{_SDK_VERSION})")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _collect_cache_summary() -> tuple[str | None, str | None]:
|
|
242
|
+
"""Collect transcript cache summary and optional note."""
|
|
243
|
+
try:
|
|
244
|
+
cache_stats = get_transcript_cache_stats()
|
|
245
|
+
except Exception:
|
|
246
|
+
return "[dim]Saved run history[/dim]: unavailable", None
|
|
247
|
+
|
|
248
|
+
runs_text = f"{cache_stats.entry_count} runs saved"
|
|
249
|
+
if cache_stats.total_bytes:
|
|
250
|
+
size_part = f" · {_format_size(cache_stats.total_bytes)} used"
|
|
251
|
+
else:
|
|
252
|
+
size_part = ""
|
|
253
|
+
|
|
254
|
+
cache_line = (
|
|
255
|
+
f"[dim]Saved run history[/dim]: {runs_text}{size_part}"
|
|
256
|
+
f" · {cache_stats.cache_dir}"
|
|
257
|
+
)
|
|
258
|
+
return cache_line, None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _display_cache_summary(
|
|
262
|
+
console: Console, slash_mode: bool, cache_line: str | None, cache_note: str | None
|
|
263
|
+
) -> None:
|
|
264
|
+
"""Render the cache summary details."""
|
|
265
|
+
if cache_line:
|
|
266
|
+
console.print(cache_line)
|
|
267
|
+
if cache_note and not slash_mode:
|
|
268
|
+
console.print(cache_note)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _create_and_test_client(
|
|
272
|
+
config: dict, console: Console, *, compact: bool = False
|
|
273
|
+
) -> Client:
|
|
171
274
|
"""Create client and test connection by fetching resources."""
|
|
172
275
|
# Try to create client
|
|
173
276
|
client = Client(
|
|
@@ -182,7 +285,7 @@ def _create_and_test_client(config: dict, console: Console) -> Client:
|
|
|
182
285
|
None, # We'll pass ctx later
|
|
183
286
|
"[bold blue]Checking GL AIP status…[/bold blue]",
|
|
184
287
|
console_override=console,
|
|
185
|
-
spinner_style=
|
|
288
|
+
spinner_style=INFO,
|
|
186
289
|
) as status_indicator:
|
|
187
290
|
update_spinner(status_indicator, "[bold blue]Fetching agents…[/bold blue]")
|
|
188
291
|
agents = client.list_agents()
|
|
@@ -195,45 +298,60 @@ def _create_and_test_client(config: dict, console: Console) -> Client:
|
|
|
195
298
|
|
|
196
299
|
# Create status table
|
|
197
300
|
table = AIPTable(title="🔗 GL AIP Status")
|
|
198
|
-
table.add_column("Resource", style=
|
|
199
|
-
table.add_column("Count", style=
|
|
200
|
-
table.add_column("Status", style=
|
|
301
|
+
table.add_column("Resource", style=INFO, width=15)
|
|
302
|
+
table.add_column("Count", style=NEUTRAL, width=10)
|
|
303
|
+
table.add_column("Status", style=SUCCESS_STYLE, width=15)
|
|
201
304
|
|
|
202
|
-
table.add_row("Agents", str(len(agents)),
|
|
203
|
-
table.add_row("Tools", str(len(tools)),
|
|
204
|
-
table.add_row("MCPs", str(len(mcps)),
|
|
305
|
+
table.add_row("Agents", str(len(agents)), AVAILABLE_STATUS)
|
|
306
|
+
table.add_row("Tools", str(len(tools)), AVAILABLE_STATUS)
|
|
307
|
+
table.add_row("MCPs", str(len(mcps)), AVAILABLE_STATUS)
|
|
205
308
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
f"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
309
|
+
if compact:
|
|
310
|
+
connection_summary = "GL AIP reachable"
|
|
311
|
+
console.print(
|
|
312
|
+
f"[dim]• Base URL[/dim]: {client.api_url} ({connection_summary})"
|
|
313
|
+
)
|
|
314
|
+
console.print(f"[dim]• Agent timeout[/dim]: {DEFAULT_AGENT_RUN_TIMEOUT}s")
|
|
315
|
+
console.print(
|
|
316
|
+
f"[dim]• Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}"
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
console.print( # pragma: no cover - UI display formatting
|
|
320
|
+
AIPPanel(
|
|
321
|
+
f"[{SUCCESS_STYLE}]✅ Connected to GL AIP[/]\n"
|
|
322
|
+
f"🔗 API URL: {client.api_url}\n"
|
|
323
|
+
f"{ICON_AGENT} Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
|
|
324
|
+
title="🚀 Connection Status",
|
|
325
|
+
border_style=SUCCESS,
|
|
326
|
+
)
|
|
213
327
|
)
|
|
214
|
-
)
|
|
215
328
|
|
|
216
|
-
|
|
329
|
+
console.print(table) # pragma: no cover - UI display formatting
|
|
217
330
|
|
|
218
331
|
except Exception as e:
|
|
219
332
|
# Show AIP Ready status even if connection fails
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
333
|
+
if compact:
|
|
334
|
+
status_text = "API call failed"
|
|
335
|
+
console.print(f"[dim]• Base URL[/dim]: {client.api_url} ({status_text})")
|
|
336
|
+
console.print(f"[{ERROR_STYLE}]• Error[/]: {e}")
|
|
337
|
+
console.print(
|
|
338
|
+
"[dim]• Tip[/dim]: Check network connectivity or API permissions and try again."
|
|
339
|
+
)
|
|
340
|
+
console.print("[dim]• Resources[/dim]: unavailable")
|
|
341
|
+
else:
|
|
342
|
+
console.print(
|
|
343
|
+
AIPPanel(
|
|
344
|
+
f"[{WARNING_STYLE}]⚠️ Connection established but API call failed[/]\n"
|
|
345
|
+
f"🔗 API URL: {client.api_url}\n"
|
|
346
|
+
f"❌ Error: {e}\n\n"
|
|
347
|
+
f"💡 This usually means:\n"
|
|
348
|
+
f" • Network connectivity issues\n"
|
|
349
|
+
f" • API permissions problems\n"
|
|
350
|
+
f" • Backend service issues",
|
|
351
|
+
title="⚠️ Partial Connection",
|
|
352
|
+
border_style=WARNING,
|
|
353
|
+
)
|
|
235
354
|
)
|
|
236
|
-
)
|
|
237
355
|
|
|
238
356
|
return client
|
|
239
357
|
|
|
@@ -242,7 +360,7 @@ def _handle_connection_error(config: dict, console: Console, error: Exception) -
|
|
|
242
360
|
"""Handle connection errors and show troubleshooting information."""
|
|
243
361
|
console.print(
|
|
244
362
|
AIPPanel(
|
|
245
|
-
f"[
|
|
363
|
+
f"[{ERROR_STYLE}]❌ Connection failed[/]\n\n"
|
|
246
364
|
f"🔍 Error: {error}\n\n"
|
|
247
365
|
f"💡 Troubleshooting steps:\n"
|
|
248
366
|
f" • Verify your API URL and key are correct\n"
|
|
@@ -250,7 +368,7 @@ def _handle_connection_error(config: dict, console: Console, error: Exception) -
|
|
|
250
368
|
f" • Run 'aip configure' to update credentials\n"
|
|
251
369
|
f" • Run 'aip config list' to check configuration",
|
|
252
370
|
title="❌ Connection Error",
|
|
253
|
-
border_style=
|
|
371
|
+
border_style=ERROR,
|
|
254
372
|
)
|
|
255
373
|
)
|
|
256
374
|
sys.exit(1)
|
|
@@ -260,20 +378,14 @@ def _handle_connection_error(config: dict, console: Console, error: Exception) -
|
|
|
260
378
|
@click.pass_context
|
|
261
379
|
def status(ctx: Any) -> None:
|
|
262
380
|
"""Show connection status and basic info."""
|
|
263
|
-
config = {}
|
|
381
|
+
config: dict = {}
|
|
382
|
+
console: Console | None = None
|
|
264
383
|
try:
|
|
265
|
-
console =
|
|
266
|
-
|
|
267
|
-
# Display AIP status banner
|
|
268
|
-
branding = AIPBranding.create_from_sdk(
|
|
269
|
-
sdk_version=_SDK_VERSION, package_name="glaip-sdk"
|
|
270
|
-
)
|
|
271
|
-
branding.display_welcome_panel(title="🚀 AIP Status")
|
|
384
|
+
console, slash_mode = _resolve_status_console(ctx)
|
|
385
|
+
_render_status_heading(console, slash_mode)
|
|
272
386
|
|
|
273
|
-
|
|
274
|
-
console
|
|
275
|
-
f"\n[bold green]✅ AIP - Ready[/bold green] (SDK v{_SDK_VERSION})"
|
|
276
|
-
)
|
|
387
|
+
cache_line, cache_note = _collect_cache_summary()
|
|
388
|
+
_display_cache_summary(console, slash_mode, cache_line, cache_note)
|
|
277
389
|
|
|
278
390
|
# Load and merge configuration
|
|
279
391
|
config = _load_and_merge_config(ctx)
|
|
@@ -281,14 +393,14 @@ def status(ctx: Any) -> None:
|
|
|
281
393
|
# Validate configuration
|
|
282
394
|
_validate_config_and_show_error(config, console)
|
|
283
395
|
|
|
284
|
-
# Create and test client connection
|
|
285
|
-
client = _create_and_test_client(config, console)
|
|
396
|
+
# Create and test client connection using unified compact layout
|
|
397
|
+
client = _create_and_test_client(config, console, compact=True)
|
|
286
398
|
client.close()
|
|
287
399
|
|
|
288
400
|
except Exception as e:
|
|
289
401
|
# Handle any unexpected errors during the process
|
|
290
|
-
|
|
291
|
-
_handle_connection_error(config
|
|
402
|
+
fallback_console = console or Console()
|
|
403
|
+
_handle_connection_error(config or {}, fallback_console, e)
|
|
292
404
|
|
|
293
405
|
|
|
294
406
|
@main.command()
|
|
@@ -330,7 +442,7 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
330
442
|
"[bold blue]🔄 Updating AIP SDK...[/bold blue]\n\n"
|
|
331
443
|
"📦 This will update the package from PyPI\n"
|
|
332
444
|
"💡 Use --check-only to just check for updates",
|
|
333
|
-
title="
|
|
445
|
+
title="Update Process",
|
|
334
446
|
border_style="blue",
|
|
335
447
|
padding=(0, 1),
|
|
336
448
|
)
|
|
@@ -352,11 +464,11 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
352
464
|
|
|
353
465
|
console.print(
|
|
354
466
|
AIPPanel(
|
|
355
|
-
"[
|
|
467
|
+
f"[{SUCCESS_STYLE}]✅ Update successful![/]\n\n"
|
|
356
468
|
"🔄 AIP SDK has been updated to the latest version\n"
|
|
357
469
|
"💡 Restart your terminal or run 'aip --version' to verify",
|
|
358
470
|
title="🎉 Update Complete",
|
|
359
|
-
border_style=
|
|
471
|
+
border_style=SUCCESS,
|
|
360
472
|
padding=(0, 1),
|
|
361
473
|
)
|
|
362
474
|
)
|
|
@@ -373,14 +485,14 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
373
485
|
except subprocess.CalledProcessError as e:
|
|
374
486
|
console.print(
|
|
375
487
|
AIPPanel(
|
|
376
|
-
f"[
|
|
488
|
+
f"[{ERROR_STYLE}]❌ Update failed[/]\n\n"
|
|
377
489
|
f"🔍 Error: {e.stderr}\n\n"
|
|
378
490
|
"💡 Troubleshooting:\n"
|
|
379
491
|
" • Check your internet connection\n"
|
|
380
492
|
" • Try running: pip install --upgrade glaip-sdk\n"
|
|
381
493
|
" • Check if you have write permissions",
|
|
382
494
|
title="❌ Update Error",
|
|
383
|
-
border_style=
|
|
495
|
+
border_style=ERROR,
|
|
384
496
|
padding=(0, 1),
|
|
385
497
|
)
|
|
386
498
|
)
|
|
@@ -389,11 +501,11 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
389
501
|
except ImportError:
|
|
390
502
|
console.print(
|
|
391
503
|
AIPPanel(
|
|
392
|
-
"[
|
|
504
|
+
f"[{ERROR_STYLE}]❌ Rich library not available[/]\n\n"
|
|
393
505
|
"💡 Install rich: pip install rich\n"
|
|
394
506
|
" Then try: aip update",
|
|
395
507
|
title="❌ Missing Dependency",
|
|
396
|
-
border_style=
|
|
508
|
+
border_style=ERROR,
|
|
397
509
|
)
|
|
398
510
|
)
|
|
399
511
|
sys.exit(1)
|
glaip_sdk/cli/pager.py
CHANGED
|
@@ -6,6 +6,7 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import importlib
|
|
9
10
|
import io
|
|
10
11
|
import os
|
|
11
12
|
import platform
|
|
@@ -43,7 +44,7 @@ def _get_console() -> Console:
|
|
|
43
44
|
"""
|
|
44
45
|
global console
|
|
45
46
|
try:
|
|
46
|
-
|
|
47
|
+
cli_utils = importlib.import_module("glaip_sdk.cli.utils")
|
|
47
48
|
except Exception: # pragma: no cover - fallback during import cycles
|
|
48
49
|
cli_utils = None
|
|
49
50
|
|
|
@@ -12,6 +12,25 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _looks_like_file_path(value: str) -> bool:
|
|
19
|
+
"""Check if string looks like a file reference.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
value: String to check for file-like patterns
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
True if the string appears to be a file path
|
|
26
|
+
"""
|
|
27
|
+
return (
|
|
28
|
+
value.lower().endswith((".json", ".yaml", ".yml"))
|
|
29
|
+
or "/" in value
|
|
30
|
+
or "\\" in value # Path separators
|
|
31
|
+
or value.startswith(("./", "../")) # Relative paths
|
|
32
|
+
or value.count(".") > 1 # Likely a filename with extension
|
|
33
|
+
)
|
|
15
34
|
|
|
16
35
|
|
|
17
36
|
def _format_file_error(
|
|
@@ -40,16 +59,16 @@ def _format_file_error(
|
|
|
40
59
|
|
|
41
60
|
|
|
42
61
|
def _parse_json_from_file(file_path_str: str) -> Any:
|
|
43
|
-
"""Parse JSON from a file path.
|
|
62
|
+
"""Parse JSON or YAML from a file path.
|
|
44
63
|
|
|
45
64
|
Args:
|
|
46
|
-
file_path_str: Path to the JSON file (without @ prefix).
|
|
65
|
+
file_path_str: Path to the JSON or YAML file (without @ prefix).
|
|
47
66
|
|
|
48
67
|
Returns:
|
|
49
68
|
Parsed dictionary from file.
|
|
50
69
|
|
|
51
70
|
Raises:
|
|
52
|
-
click.ClickException: If file not found, not readable, empty, or invalid
|
|
71
|
+
click.ClickException: If file not found, not readable, empty, or invalid format.
|
|
53
72
|
"""
|
|
54
73
|
# Resolve relative paths against CWD
|
|
55
74
|
file_path = Path(file_path_str)
|
|
@@ -86,18 +105,35 @@ def _parse_json_from_file(file_path_str: str) -> Any:
|
|
|
86
105
|
_format_file_error("File is empty", file_path_str, file_path)
|
|
87
106
|
)
|
|
88
107
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
# Determine file format and parse accordingly
|
|
109
|
+
file_ext = file_path.suffix.lower()
|
|
110
|
+
|
|
111
|
+
if file_ext in [".yaml", ".yml"]:
|
|
112
|
+
# Parse YAML from file content
|
|
113
|
+
try:
|
|
114
|
+
return yaml.safe_load(content)
|
|
115
|
+
except yaml.YAMLError as e:
|
|
116
|
+
raise click.ClickException(
|
|
117
|
+
_format_file_error(
|
|
118
|
+
"Invalid YAML in file",
|
|
119
|
+
file_path_str,
|
|
120
|
+
file_path,
|
|
121
|
+
detail=f"Error: {e}",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
# Default to JSON parsing
|
|
126
|
+
try:
|
|
127
|
+
return json.loads(content)
|
|
128
|
+
except json.JSONDecodeError as e:
|
|
129
|
+
raise click.ClickException(
|
|
130
|
+
_format_file_error(
|
|
131
|
+
"Invalid JSON in file",
|
|
132
|
+
file_path_str,
|
|
133
|
+
file_path,
|
|
134
|
+
detail=f"Error: {e.msg} at line {e.lineno}, column {e.colno}",
|
|
135
|
+
)
|
|
99
136
|
)
|
|
100
|
-
)
|
|
101
137
|
|
|
102
138
|
|
|
103
139
|
def parse_json_input(value: str | None) -> Any:
|
|
@@ -119,6 +155,9 @@ def parse_json_input(value: str | None) -> Any:
|
|
|
119
155
|
>>> parse_json_input('@/path/to/config.json')
|
|
120
156
|
# Returns content of config.json parsed as JSON
|
|
121
157
|
|
|
158
|
+
>>> parse_json_input('/path/to/config.json')
|
|
159
|
+
# Fallback: treats as file path if JSON parsing fails
|
|
160
|
+
|
|
122
161
|
>>> parse_json_input(None)
|
|
123
162
|
None
|
|
124
163
|
"""
|
|
@@ -134,6 +173,15 @@ def parse_json_input(value: str | None) -> Any:
|
|
|
134
173
|
try:
|
|
135
174
|
return json.loads(value)
|
|
136
175
|
except json.JSONDecodeError as e:
|
|
176
|
+
# Check if the value looks like a file path and provide helpful hint
|
|
177
|
+
if _looks_like_file_path(trimmed):
|
|
178
|
+
raise click.ClickException(
|
|
179
|
+
f"Invalid JSON in inline value\n"
|
|
180
|
+
f"Error: {e.msg} at line {e.lineno}, column {e.colno}\n"
|
|
181
|
+
f"\n💡 Did you mean to load this from a file? "
|
|
182
|
+
f"File-based config values should start with @ (e.g., @{trimmed})"
|
|
183
|
+
)
|
|
184
|
+
|
|
137
185
|
raise click.ClickException(
|
|
138
186
|
f"Invalid JSON in inline value\n"
|
|
139
187
|
f"Error: {e.msg} at line {e.lineno}, column {e.colno}"
|
glaip_sdk/cli/resolution.py
CHANGED
|
@@ -12,6 +12,7 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
import click
|
|
14
14
|
|
|
15
|
+
from glaip_sdk.branding import ACCENT_STYLE
|
|
15
16
|
from glaip_sdk.cli.utils import resolve_resource, spinner_context
|
|
16
17
|
|
|
17
18
|
|
|
@@ -55,7 +56,9 @@ def resolve_resource_reference(
|
|
|
55
56
|
if spinner_message is not None
|
|
56
57
|
else f"[bold blue]Fetching {label}…[/bold blue]"
|
|
57
58
|
)
|
|
58
|
-
with spinner_context(
|
|
59
|
+
with spinner_context(
|
|
60
|
+
ctx, message, spinner_style=ACCENT_STYLE
|
|
61
|
+
) as status_indicator:
|
|
59
62
|
return resolve_resource(
|
|
60
63
|
ctx,
|
|
61
64
|
reference,
|
glaip_sdk/cli/slash/__init__.py
CHANGED
|
@@ -7,12 +7,11 @@ Authors:
|
|
|
7
7
|
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
8
8
|
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
9
9
|
from glaip_sdk.cli.commands.configure import configure_command, load_config
|
|
10
|
+
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
11
|
+
from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT
|
|
12
|
+
from glaip_sdk.cli.slash.session import SlashSession
|
|
10
13
|
from glaip_sdk.cli.utils import get_client
|
|
11
14
|
|
|
12
|
-
from .agent_session import AgentRunSession
|
|
13
|
-
from .prompt import _HAS_PROMPT_TOOLKIT
|
|
14
|
-
from .session import SlashSession
|
|
15
|
-
|
|
16
15
|
__all__ = [
|
|
17
16
|
"AgentRunSession",
|
|
18
17
|
"SlashSession",
|