glaip-sdk 0.2.1__py3-none-any.whl → 0.2.2__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 +8 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/commands/agents.py +131 -5
- glaip_sdk/cli/commands/mcps.py +16 -0
- glaip_sdk/cli/commands/models.py +8 -0
- glaip_sdk/cli/commands/tools.py +8 -0
- glaip_sdk/cli/commands/transcripts.py +8 -0
- glaip_sdk/cli/constants.py +35 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/display.py +34 -19
- glaip_sdk/cli/main.py +13 -4
- glaip_sdk/cli/masking.py +8 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/slash/agent_session.py +54 -7
- glaip_sdk/cli/slash/prompt.py +5 -0
- glaip_sdk/cli/slash/session.py +206 -2
- glaip_sdk/cli/transcript/viewer.py +226 -0
- glaip_sdk/cli/update_notifier.py +2 -2
- glaip_sdk/cli/utils.py +84 -27
- glaip_sdk/client/_agent_payloads.py +30 -0
- glaip_sdk/client/agents.py +144 -0
- glaip_sdk/client/main.py +5 -0
- glaip_sdk/client/run_rendering.py +66 -0
- glaip_sdk/utils/serialization.py +16 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/METADATA +1 -1
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/RECORD +28 -27
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/entry_points.txt +0 -0
glaip_sdk/_version.py
CHANGED
|
@@ -14,6 +14,14 @@ except Exception: # pragma: no cover - extremely unlikely
|
|
|
14
14
|
PackageNotFoundError = Exception # type: ignore
|
|
15
15
|
|
|
16
16
|
def version(_: str) -> str: # type: ignore
|
|
17
|
+
"""Fallback version function when importlib.metadata is unavailable.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
_: Package name (ignored in fallback).
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Default dev version string.
|
|
24
|
+
"""
|
|
17
25
|
return "0.0.0.dev0"
|
|
18
26
|
|
|
19
27
|
|
glaip_sdk/branding.py
CHANGED
|
@@ -90,6 +90,14 @@ GDP Labs AI Agents Package
|
|
|
90
90
|
# ---- small helpers --------------------------------------------------------
|
|
91
91
|
@staticmethod
|
|
92
92
|
def _auto_version(package_name: str | None) -> str:
|
|
93
|
+
"""Auto-detect version from environment, package metadata, or fallback.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
package_name: Optional package name to read version from installed metadata.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Version string from AIP_VERSION env var, package metadata, or SDK_VERSION fallback.
|
|
100
|
+
"""
|
|
93
101
|
# Priority: env → package metadata → fallback
|
|
94
102
|
env_version = os.getenv("AIP_VERSION")
|
|
95
103
|
if env_version:
|
|
@@ -103,6 +111,11 @@ GDP Labs AI Agents Package
|
|
|
103
111
|
|
|
104
112
|
@staticmethod
|
|
105
113
|
def _make_console() -> Console:
|
|
114
|
+
"""Create a Rich Console instance respecting NO_COLOR environment variables.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Console instance with color system configured based on environment.
|
|
118
|
+
"""
|
|
106
119
|
# Respect NO_COLOR/AIP_NO_COLOR environment variables
|
|
107
120
|
no_color_env = os.getenv("NO_COLOR") is not None or os.getenv("AIP_NO_COLOR") is not None
|
|
108
121
|
if no_color_env:
|
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import json
|
|
10
10
|
import os
|
|
11
11
|
from collections.abc import Mapping
|
|
12
|
+
from copy import deepcopy
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
@@ -18,6 +19,7 @@ from rich.console import Console
|
|
|
18
19
|
from glaip_sdk.branding import (
|
|
19
20
|
ACCENT_STYLE,
|
|
20
21
|
ERROR_STYLE,
|
|
22
|
+
HINT_PREFIX_STYLE,
|
|
21
23
|
INFO,
|
|
22
24
|
SUCCESS,
|
|
23
25
|
SUCCESS_STYLE,
|
|
@@ -33,6 +35,7 @@ from glaip_sdk.cli.agent_config import (
|
|
|
33
35
|
sanitize_agent_config_for_cli as sanitize_agent_config,
|
|
34
36
|
)
|
|
35
37
|
from glaip_sdk.cli.context import get_ctx_value, output_flags
|
|
38
|
+
from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
36
39
|
from glaip_sdk.cli.display import (
|
|
37
40
|
build_resource_result_data,
|
|
38
41
|
display_agent_run_suggestions,
|
|
@@ -61,6 +64,7 @@ from glaip_sdk.cli.utils import (
|
|
|
61
64
|
build_renderer,
|
|
62
65
|
coerce_to_row,
|
|
63
66
|
get_client,
|
|
67
|
+
in_slash_mode,
|
|
64
68
|
output_list,
|
|
65
69
|
output_result,
|
|
66
70
|
spinner_context,
|
|
@@ -88,6 +92,8 @@ console = Console()
|
|
|
88
92
|
# Error message constants
|
|
89
93
|
AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
90
94
|
|
|
95
|
+
# Instruction preview controls
|
|
96
|
+
|
|
91
97
|
|
|
92
98
|
def _safe_agent_attribute(agent: Any, name: str) -> Any:
|
|
93
99
|
"""Return attribute value for ``name`` while filtering Mock sentinels."""
|
|
@@ -307,12 +313,92 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
|
|
|
307
313
|
return result_data
|
|
308
314
|
|
|
309
315
|
|
|
310
|
-
def
|
|
316
|
+
def _clamp_instruction_preview_limit(limit: int | None) -> int:
|
|
317
|
+
"""Normalise preview limit; 0 disables trimming."""
|
|
318
|
+
default = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
319
|
+
if limit is None: # pragma: no cover
|
|
320
|
+
return default
|
|
321
|
+
try:
|
|
322
|
+
limit_value = int(limit)
|
|
323
|
+
except (TypeError, ValueError): # pragma: no cover - defensive parsing
|
|
324
|
+
return default
|
|
325
|
+
|
|
326
|
+
if limit_value <= 0:
|
|
327
|
+
return 0
|
|
328
|
+
|
|
329
|
+
return limit_value
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _build_instruction_preview(value: Any, limit: int) -> tuple[Any, bool]:
|
|
333
|
+
"""Return a trimmed preview for long instruction strings."""
|
|
334
|
+
if not isinstance(value, str) or limit <= 0: # pragma: no cover
|
|
335
|
+
return value, False
|
|
336
|
+
|
|
337
|
+
if len(value) <= limit:
|
|
338
|
+
return value, False
|
|
339
|
+
|
|
340
|
+
trimmed_value = value[:limit].rstrip()
|
|
341
|
+
preview = f"{trimmed_value}\n\n... (preview trimmed)"
|
|
342
|
+
return preview, True
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _prepare_agent_details_payload(
|
|
346
|
+
data: dict[str, Any],
|
|
347
|
+
*,
|
|
348
|
+
instruction_preview_limit: int,
|
|
349
|
+
) -> tuple[dict[str, Any], bool]:
|
|
350
|
+
"""Return payload ready for rendering plus trim indicator."""
|
|
351
|
+
payload = deepcopy(data)
|
|
352
|
+
trimmed = False
|
|
353
|
+
if instruction_preview_limit > 0:
|
|
354
|
+
preview, trimmed = _build_instruction_preview(payload.get("instruction"), instruction_preview_limit)
|
|
355
|
+
if trimmed:
|
|
356
|
+
payload["instruction"] = preview
|
|
357
|
+
return payload, trimmed
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _show_instruction_trim_hint(
|
|
361
|
+
ctx: Any,
|
|
362
|
+
*,
|
|
363
|
+
trimmed: bool,
|
|
364
|
+
preview_limit: int,
|
|
365
|
+
) -> None:
|
|
366
|
+
"""Render hint describing how to expand or collapse the instruction preview."""
|
|
367
|
+
if not trimmed or preview_limit <= 0:
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
view = get_ctx_value(ctx, "view", "rich") if ctx is not None else "rich"
|
|
371
|
+
if view != "rich": # pragma: no cover - non-rich view handling
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
suffix = f"[dim](preview: {preview_limit:,} chars)[/]"
|
|
375
|
+
if in_slash_mode(ctx):
|
|
376
|
+
console.print(
|
|
377
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] Use '/details' again to toggle between trimmed and full prompts {suffix}"
|
|
378
|
+
)
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
console.print( # pragma: no cover - fallback hint rendering
|
|
382
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] Run 'aip agents get <agent> --instruction-preview <n>' "
|
|
383
|
+
f"to control prompt preview length {suffix}"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _display_agent_details(
|
|
388
|
+
ctx: Any,
|
|
389
|
+
client: Any,
|
|
390
|
+
agent: Any,
|
|
391
|
+
*,
|
|
392
|
+
instruction_preview_limit: int | None = None,
|
|
393
|
+
) -> None:
|
|
311
394
|
"""Display full agent details using raw API data to preserve ALL fields."""
|
|
312
395
|
if agent is None:
|
|
313
396
|
handle_rich_output(ctx, markup_text(f"[{ERROR_STYLE}]❌ No agent provided[/]"))
|
|
314
397
|
return
|
|
315
398
|
|
|
399
|
+
preview_limit = _clamp_instruction_preview_limit(instruction_preview_limit)
|
|
400
|
+
trimmed_instruction = False
|
|
401
|
+
|
|
316
402
|
# Try to fetch and format raw agent data first
|
|
317
403
|
with spinner_context(
|
|
318
404
|
ctx,
|
|
@@ -324,9 +410,13 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
324
410
|
if formatted_data:
|
|
325
411
|
# Use raw API data - this preserves ALL fields including account_id
|
|
326
412
|
panel_title = f"{ICON_AGENT} {formatted_data.get('name', 'Unknown')}"
|
|
413
|
+
payload, trimmed_instruction = _prepare_agent_details_payload(
|
|
414
|
+
formatted_data,
|
|
415
|
+
instruction_preview_limit=preview_limit,
|
|
416
|
+
)
|
|
327
417
|
output_result(
|
|
328
418
|
ctx,
|
|
329
|
-
|
|
419
|
+
payload,
|
|
330
420
|
title=panel_title,
|
|
331
421
|
)
|
|
332
422
|
else:
|
|
@@ -344,12 +434,22 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
344
434
|
result_data = _format_fallback_agent_data(client, agent)
|
|
345
435
|
|
|
346
436
|
# Display using output_result
|
|
437
|
+
payload, trimmed_instruction = _prepare_agent_details_payload(
|
|
438
|
+
result_data,
|
|
439
|
+
instruction_preview_limit=preview_limit,
|
|
440
|
+
)
|
|
347
441
|
output_result(
|
|
348
442
|
ctx,
|
|
349
|
-
|
|
443
|
+
payload,
|
|
350
444
|
title="Agent Details",
|
|
351
445
|
)
|
|
352
446
|
|
|
447
|
+
_show_instruction_trim_hint(
|
|
448
|
+
ctx,
|
|
449
|
+
trimmed=trimmed_instruction,
|
|
450
|
+
preview_limit=preview_limit,
|
|
451
|
+
)
|
|
452
|
+
|
|
353
453
|
|
|
354
454
|
@click.group(name="agents", no_args_is_help=True)
|
|
355
455
|
def agents_group() -> None:
|
|
@@ -439,6 +539,14 @@ def list_agents(
|
|
|
439
539
|
|
|
440
540
|
# Transform function for safe attribute access
|
|
441
541
|
def transform_agent(agent: Any) -> dict[str, Any]:
|
|
542
|
+
"""Transform an agent object to a display row dictionary.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
agent: Agent object to transform.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
Dictionary with id, name, type, framework, and version fields.
|
|
549
|
+
"""
|
|
442
550
|
row = coerce_to_row(agent, ["id", "name", "type", "framework", "version"])
|
|
443
551
|
# Ensure id is always a string
|
|
444
552
|
row["id"] = str(row["id"])
|
|
@@ -488,9 +596,22 @@ def list_agents(
|
|
|
488
596
|
type=click.Path(dir_okay=False, writable=True),
|
|
489
597
|
help="Export complete agent configuration to file (format auto-detected from .json/.yaml extension)",
|
|
490
598
|
)
|
|
599
|
+
@click.option(
|
|
600
|
+
"--instruction-preview",
|
|
601
|
+
type=int,
|
|
602
|
+
default=0,
|
|
603
|
+
show_default=True,
|
|
604
|
+
help="Instruction preview length when printing instructions (0 shows full prompt).",
|
|
605
|
+
)
|
|
491
606
|
@output_flags()
|
|
492
607
|
@click.pass_context
|
|
493
|
-
def get(
|
|
608
|
+
def get(
|
|
609
|
+
ctx: Any,
|
|
610
|
+
agent_ref: str,
|
|
611
|
+
select: int | None,
|
|
612
|
+
export: str | None,
|
|
613
|
+
instruction_preview: int,
|
|
614
|
+
) -> None:
|
|
494
615
|
r"""Get agent details.
|
|
495
616
|
|
|
496
617
|
\b
|
|
@@ -519,7 +640,12 @@ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> Non
|
|
|
519
640
|
)
|
|
520
641
|
|
|
521
642
|
# Display full agent details using the standardized helper
|
|
522
|
-
_display_agent_details(
|
|
643
|
+
_display_agent_details(
|
|
644
|
+
ctx,
|
|
645
|
+
client,
|
|
646
|
+
agent,
|
|
647
|
+
instruction_preview_limit=instruction_preview,
|
|
648
|
+
)
|
|
523
649
|
|
|
524
650
|
# Show run suggestions via centralized display helper
|
|
525
651
|
handle_rich_output(ctx, display_agent_run_suggestions(agent))
|
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -529,6 +529,14 @@ def list_mcps(ctx: Any) -> None:
|
|
|
529
529
|
|
|
530
530
|
# Transform function for safe dictionary access
|
|
531
531
|
def transform_mcp(mcp: Any) -> dict[str, Any]:
|
|
532
|
+
"""Transform an MCP object to a display row dictionary.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
mcp: MCP object to transform.
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Dictionary with id, name, and config fields.
|
|
539
|
+
"""
|
|
532
540
|
row = coerce_to_row(mcp, ["id", "name", "config"])
|
|
533
541
|
# Ensure id is always a string
|
|
534
542
|
row["id"] = str(row["id"])
|
|
@@ -897,6 +905,14 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
897
905
|
|
|
898
906
|
# Transform function for safe dictionary access
|
|
899
907
|
def transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
|
|
908
|
+
"""Transform a tool dictionary to a display row dictionary.
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
tool: Tool dictionary to transform.
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
Dictionary with name and description fields.
|
|
915
|
+
"""
|
|
900
916
|
return {
|
|
901
917
|
"name": tool.get("name", "N/A"),
|
|
902
918
|
"description": tool.get("description", "N/A")[:47] + "..."
|
glaip_sdk/cli/commands/models.py
CHANGED
|
@@ -50,6 +50,14 @@ def list_models(ctx: Any) -> None:
|
|
|
50
50
|
|
|
51
51
|
# Transform function for safe dictionary access
|
|
52
52
|
def transform_model(model: dict[str, Any]) -> dict[str, Any]:
|
|
53
|
+
"""Transform a model dictionary to a display row dictionary.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
model: Model dictionary to transform.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary with id, provider, name, and base_url fields.
|
|
60
|
+
"""
|
|
53
61
|
return {
|
|
54
62
|
"id": str(model.get("id", "N/A")),
|
|
55
63
|
"provider": model.get("provider", "N/A"),
|
glaip_sdk/cli/commands/tools.py
CHANGED
|
@@ -211,6 +211,14 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
|
|
|
211
211
|
|
|
212
212
|
# Transform function for safe dictionary access
|
|
213
213
|
def transform_tool(tool: Any) -> dict[str, Any]:
|
|
214
|
+
"""Transform a tool object to a display row dictionary.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
tool: Tool object to transform.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dictionary with id, name, and framework fields.
|
|
221
|
+
"""
|
|
214
222
|
row = coerce_to_row(tool, ["id", "name", "framework"])
|
|
215
223
|
# Ensure id is always a string
|
|
216
224
|
row["id"] = str(row["id"])
|
|
@@ -156,6 +156,14 @@ def _launch_transcript_viewer(
|
|
|
156
156
|
viewer_ctx = _build_viewer_context(entry, meta, events)
|
|
157
157
|
|
|
158
158
|
def _export(destination: Path) -> Path:
|
|
159
|
+
"""Export cached transcript to destination.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
destination: Path to export transcript to.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Path to exported transcript file.
|
|
166
|
+
"""
|
|
159
167
|
return export_cached_transcript(destination=destination, run_id=entry.run_id)
|
|
160
168
|
|
|
161
169
|
run_viewer_session(target_console, viewer_ctx, _export, initial_view=initial_view)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""CLI-specific constants for glaip-sdk.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Minimum length that forces multiline YAML strings to be rendered using the literal
|
|
8
|
+
# block style. This prevents long prompts and instructions from being inlined.
|
|
9
|
+
LITERAL_STRING_THRESHOLD = 200
|
|
10
|
+
|
|
11
|
+
# Masking configuration
|
|
12
|
+
MASKING_ENABLED = True
|
|
13
|
+
MASK_SENSITIVE_FIELDS = {
|
|
14
|
+
"api_key",
|
|
15
|
+
"apikey",
|
|
16
|
+
"token",
|
|
17
|
+
"access_token",
|
|
18
|
+
"secret",
|
|
19
|
+
"client_secret",
|
|
20
|
+
"password",
|
|
21
|
+
"private_key",
|
|
22
|
+
"bearer",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Table + pager behaviour
|
|
26
|
+
TABLE_SORT_ENABLED = True
|
|
27
|
+
PAGER_MODE = "auto" # valid values: "auto", "on", "off"
|
|
28
|
+
PAGER_WRAP_LINES = False
|
|
29
|
+
PAGER_HEADER_ENABLED = True
|
|
30
|
+
|
|
31
|
+
# Update notification toggle
|
|
32
|
+
UPDATE_CHECK_ENABLED = True
|
|
33
|
+
|
|
34
|
+
# Agent instruction preview defaults
|
|
35
|
+
DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT = 800
|
glaip_sdk/cli/context.py
CHANGED
|
@@ -106,6 +106,14 @@ def output_flags() -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
|
106
106
|
"""
|
|
107
107
|
|
|
108
108
|
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
109
|
+
"""Apply output flags to a click command.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
f: Click command function to decorate.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Decorated command function.
|
|
116
|
+
"""
|
|
109
117
|
f = click.option(
|
|
110
118
|
"--json",
|
|
111
119
|
"json_mode",
|
glaip_sdk/cli/display.py
CHANGED
|
@@ -17,7 +17,7 @@ from rich.text import Text
|
|
|
17
17
|
|
|
18
18
|
from glaip_sdk.branding import ERROR_STYLE, SUCCESS, SUCCESS_STYLE, WARNING_STYLE
|
|
19
19
|
from glaip_sdk.cli.rich_helpers import markup_text
|
|
20
|
-
from glaip_sdk.cli.utils import command_hint, format_command_hint
|
|
20
|
+
from glaip_sdk.cli.utils import command_hint, format_command_hint, in_slash_mode
|
|
21
21
|
from glaip_sdk.icons import ICON_AGENT, ICON_TOOL
|
|
22
22
|
from glaip_sdk.rich_components import AIPPanel
|
|
23
23
|
|
|
@@ -304,6 +304,7 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
|
|
|
304
304
|
"""Return a panel with post-creation suggestions for an agent."""
|
|
305
305
|
agent_id = getattr(agent, "id", "")
|
|
306
306
|
agent_name = getattr(agent, "name", "")
|
|
307
|
+
slash_mode = in_slash_mode()
|
|
307
308
|
run_hint_id = command_hint(
|
|
308
309
|
f'agents run {agent_id} "Your message here"',
|
|
309
310
|
slash_command=None,
|
|
@@ -313,27 +314,41 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
|
|
|
313
314
|
slash_command=None,
|
|
314
315
|
)
|
|
315
316
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
f" {format_command_hint(
|
|
321
|
-
|
|
317
|
+
content_parts: list[str] = ["[bold blue]💡 Next Steps:[/bold blue]\n\n"]
|
|
318
|
+
|
|
319
|
+
if slash_mode:
|
|
320
|
+
slash_shortcuts = "\n".join(
|
|
321
|
+
f" {format_command_hint(command, description) or command}"
|
|
322
|
+
for command, description in (
|
|
323
|
+
("/details", "Show configuration (toggle preview)"),
|
|
324
|
+
("/help", "Show command palette menu"),
|
|
325
|
+
("/exit", "Return to the palette"),
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
content_parts.append(
|
|
329
|
+
f"🚀 Start chatting with [bold]{agent_name}[/bold] right here:\n"
|
|
330
|
+
f" Type your message below and press Enter to run it immediately.\n\n"
|
|
331
|
+
f"{ICON_TOOL} Slash shortcuts:\n"
|
|
332
|
+
f"{slash_shortcuts}"
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
cli_hint_lines = [format_command_hint(hint) or hint for hint in (run_hint_id, run_hint_name) if hint]
|
|
336
|
+
if cli_hint_lines:
|
|
337
|
+
joined_hints = "\n".join(f" {hint}" for hint in cli_hint_lines)
|
|
338
|
+
content_parts.append(f"🚀 Run this agent from the CLI:\n{joined_hints}\n\n")
|
|
339
|
+
content_parts.append(
|
|
340
|
+
f"{ICON_TOOL} Available options:\n"
|
|
341
|
+
f" [dim]--chat-history[/dim] Include previous conversation\n"
|
|
342
|
+
f" [dim]--file[/dim] Attach files\n"
|
|
343
|
+
f" [dim]--input[/dim] Alternative input method\n"
|
|
344
|
+
f" [dim]--timeout[/dim] Set execution timeout\n"
|
|
345
|
+
f" [dim]--save[/dim] Save transcript to file\n"
|
|
346
|
+
f" [dim]--verbose[/dim] Show detailed execution\n\n"
|
|
347
|
+
f"💡 [dim]Input text can be positional OR use --input flag (both work!)[/dim]"
|
|
322
348
|
)
|
|
323
349
|
|
|
324
350
|
return AIPPanel(
|
|
325
|
-
|
|
326
|
-
f"🚀 Start chatting with [bold]{agent_name}[/bold] right here:\n"
|
|
327
|
-
f" Type your message below and press Enter to run it immediately.\n\n"
|
|
328
|
-
f"{cli_section}"
|
|
329
|
-
f"{ICON_TOOL} Available options:\n"
|
|
330
|
-
f" [dim]--chat-history[/dim] Include previous conversation\n"
|
|
331
|
-
f" [dim]--file[/dim] Attach files\n"
|
|
332
|
-
f" [dim]--input[/dim] Alternative input method\n"
|
|
333
|
-
f" [dim]--timeout[/dim] Set execution timeout\n"
|
|
334
|
-
f" [dim]--save[/dim] Save transcript to file\n"
|
|
335
|
-
f" [dim]--verbose[/dim] Show detailed execution\n\n"
|
|
336
|
-
f"💡 [dim]Input text can be positional OR use --input flag (both work!)[/dim]",
|
|
351
|
+
"".join(content_parts),
|
|
337
352
|
title=f"{ICON_AGENT} Ready to Run Agent",
|
|
338
353
|
border_style="blue",
|
|
339
354
|
padding=(0, 1),
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -387,6 +387,7 @@ def version() -> None:
|
|
|
387
387
|
)
|
|
388
388
|
def update(check_only: bool, force: bool) -> None:
|
|
389
389
|
"""Update AIP SDK to the latest version from PyPI."""
|
|
390
|
+
slash_mode = in_slash_mode()
|
|
390
391
|
try:
|
|
391
392
|
console = Console()
|
|
392
393
|
|
|
@@ -400,11 +401,15 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
400
401
|
)
|
|
401
402
|
return
|
|
402
403
|
|
|
404
|
+
update_hint = ""
|
|
405
|
+
if not slash_mode:
|
|
406
|
+
update_hint = "\n💡 Use --check-only to just check for updates"
|
|
407
|
+
|
|
403
408
|
console.print(
|
|
404
409
|
AIPPanel(
|
|
405
410
|
"[bold blue]🔄 Updating AIP SDK...[/bold blue]\n\n"
|
|
406
|
-
"📦 This will update the package from PyPI
|
|
407
|
-
"
|
|
411
|
+
"📦 This will update the package from PyPI"
|
|
412
|
+
f"{update_hint}",
|
|
408
413
|
title="Update Process",
|
|
409
414
|
border_style="blue",
|
|
410
415
|
padding=(0, 1),
|
|
@@ -422,11 +427,15 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
422
427
|
cmd.insert(5, "--force-reinstall")
|
|
423
428
|
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
424
429
|
|
|
430
|
+
verify_hint = ""
|
|
431
|
+
if not slash_mode:
|
|
432
|
+
verify_hint = "\n💡 Restart your terminal or run 'aip --version' to verify"
|
|
433
|
+
|
|
425
434
|
console.print(
|
|
426
435
|
AIPPanel(
|
|
427
436
|
f"[{SUCCESS_STYLE}]✅ Update successful![/]\n\n"
|
|
428
|
-
"🔄 AIP SDK has been updated to the latest version
|
|
429
|
-
"
|
|
437
|
+
"🔄 AIP SDK has been updated to the latest version"
|
|
438
|
+
f"{verify_hint}",
|
|
430
439
|
title="🎉 Update Complete",
|
|
431
440
|
border_style=SUCCESS,
|
|
432
441
|
padding=(0, 1),
|
glaip_sdk/cli/masking.py
CHANGED
|
@@ -6,9 +6,10 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import os
|
|
10
9
|
from typing import Any
|
|
11
10
|
|
|
11
|
+
from glaip_sdk.cli.constants import MASKING_ENABLED, MASK_SENSITIVE_FIELDS
|
|
12
|
+
|
|
12
13
|
__all__ = [
|
|
13
14
|
"mask_payload",
|
|
14
15
|
"mask_rows",
|
|
@@ -18,18 +19,6 @@ __all__ = [
|
|
|
18
19
|
"_resolve_mask_fields",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
|
-
_DEFAULT_MASK_FIELDS = {
|
|
22
|
-
"api_key",
|
|
23
|
-
"apikey",
|
|
24
|
-
"token",
|
|
25
|
-
"access_token",
|
|
26
|
-
"secret",
|
|
27
|
-
"client_secret",
|
|
28
|
-
"password",
|
|
29
|
-
"private_key",
|
|
30
|
-
"bearer",
|
|
31
|
-
}
|
|
32
|
-
|
|
33
22
|
|
|
34
23
|
def _mask_value(raw: Any) -> str:
|
|
35
24
|
"""Return a masked representation of the provided value.
|
|
@@ -90,22 +79,10 @@ def _maybe_mask_row(row: dict[str, Any], mask_fields: set[str]) -> dict[str, Any
|
|
|
90
79
|
|
|
91
80
|
|
|
92
81
|
def _resolve_mask_fields() -> set[str]:
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
set[str]: Set of field names to mask. Empty set if masking is disabled
|
|
97
|
-
via AIP_MASK_OFF environment variable, custom fields from
|
|
98
|
-
AIP_MASK_FIELDS, or default fields if neither is set.
|
|
99
|
-
"""
|
|
100
|
-
if os.getenv("AIP_MASK_OFF", "0") in {"1", "true", "on", "yes"}:
|
|
82
|
+
"""Return the configured set of fields that should be masked."""
|
|
83
|
+
if not MASKING_ENABLED:
|
|
101
84
|
return set()
|
|
102
|
-
|
|
103
|
-
env_fields = (os.getenv("AIP_MASK_FIELDS") or "").strip()
|
|
104
|
-
if env_fields:
|
|
105
|
-
parts = [part.strip().lower() for part in env_fields.split(",") if part.strip()]
|
|
106
|
-
return set(parts)
|
|
107
|
-
|
|
108
|
-
return set(_DEFAULT_MASK_FIELDS)
|
|
85
|
+
return set(MASK_SENSITIVE_FIELDS)
|
|
109
86
|
|
|
110
87
|
|
|
111
88
|
def mask_payload(payload: Any) -> Any:
|
|
@@ -115,9 +92,7 @@ def mask_payload(payload: Any) -> Any:
|
|
|
115
92
|
payload: Any data structure (dict, list, or primitive) to mask.
|
|
116
93
|
|
|
117
94
|
Returns:
|
|
118
|
-
Any: The payload with sensitive fields masked based on
|
|
119
|
-
configuration. Returns original payload if masking is disabled
|
|
120
|
-
or if an error occurs during masking.
|
|
95
|
+
Any: The payload with sensitive fields masked based on configuration.
|
|
121
96
|
"""
|
|
122
97
|
mask_fields = _resolve_mask_fields()
|
|
123
98
|
if not mask_fields:
|
|
@@ -136,8 +111,8 @@ def mask_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
136
111
|
|
|
137
112
|
Returns:
|
|
138
113
|
list[dict[str, Any]]: List of rows with sensitive fields masked based
|
|
139
|
-
on
|
|
140
|
-
|
|
114
|
+
on configuration. Returns original rows if
|
|
115
|
+
masking is disabled or if an error occurs.
|
|
141
116
|
"""
|
|
142
117
|
mask_fields = _resolve_mask_fields()
|
|
143
118
|
if not mask_fields:
|
glaip_sdk/cli/pager.py
CHANGED
|
@@ -19,6 +19,8 @@ from typing import Any
|
|
|
19
19
|
|
|
20
20
|
from rich.console import Console
|
|
21
21
|
|
|
22
|
+
from glaip_sdk.cli.constants import PAGER_HEADER_ENABLED, PAGER_MODE, PAGER_WRAP_LINES
|
|
23
|
+
|
|
22
24
|
__all__ = [
|
|
23
25
|
"console",
|
|
24
26
|
"_prepare_pager_env",
|
|
@@ -64,8 +66,7 @@ def _prepare_pager_env(clear_on_exit: bool = True) -> None:
|
|
|
64
66
|
-R : pass ANSI color escapes
|
|
65
67
|
-S : chop long lines (horizontal scroll with ←/→)
|
|
66
68
|
(No -F, no -X) so we open a full-screen pager and clear on exit.
|
|
67
|
-
Toggle wrapping
|
|
68
|
-
Power users can override via AIP_LESS_FLAGS.
|
|
69
|
+
Toggle wrapping via `PAGER_WRAP_LINES` (True drops -S).
|
|
69
70
|
|
|
70
71
|
Args:
|
|
71
72
|
clear_on_exit: Whether to clear the pager on exit (default: True)
|
|
@@ -75,10 +76,9 @@ def _prepare_pager_env(clear_on_exit: bool = True) -> None:
|
|
|
75
76
|
"""
|
|
76
77
|
os.environ.pop("LESSSECURE", None)
|
|
77
78
|
if os.getenv("LESS") is None:
|
|
78
|
-
|
|
79
|
-
base = "-R" if want_wrap else "-RS"
|
|
79
|
+
base = "-R" if PAGER_WRAP_LINES else "-RS"
|
|
80
80
|
default_flags = base if clear_on_exit else (base + "FX")
|
|
81
|
-
os.environ["LESS"] =
|
|
81
|
+
os.environ["LESS"] = default_flags
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _render_ansi(renderable: Any) -> str:
|
|
@@ -111,8 +111,7 @@ def _pager_header() -> str:
|
|
|
111
111
|
Returns:
|
|
112
112
|
str: Header text containing navigation help, or empty string if disabled
|
|
113
113
|
"""
|
|
114
|
-
|
|
115
|
-
if v in {"0", "false", "off"}:
|
|
114
|
+
if not PAGER_HEADER_ENABLED:
|
|
116
115
|
return ""
|
|
117
116
|
return "\n".join(
|
|
118
117
|
[
|
|
@@ -254,10 +253,10 @@ def _should_page_output(row_count: int, is_tty: bool) -> bool:
|
|
|
254
253
|
bool: True if output should be paginated, False otherwise
|
|
255
254
|
"""
|
|
256
255
|
active_console = _get_console()
|
|
257
|
-
|
|
258
|
-
if
|
|
256
|
+
pager_mode = (PAGER_MODE or "auto").lower()
|
|
257
|
+
if pager_mode in ("0", "off", "false"):
|
|
259
258
|
return False
|
|
260
|
-
if
|
|
259
|
+
if pager_mode in ("1", "on", "true"):
|
|
261
260
|
return is_tty
|
|
262
261
|
try:
|
|
263
262
|
term_h = active_console.size.height or 24
|