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 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:
@@ -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 _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
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
- formatted_data,
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
- result_data,
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(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> None:
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(ctx, client, agent)
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))
@@ -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] + "..."
@@ -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"),
@@ -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
- cli_section = ""
317
- if run_hint_id and run_hint_name:
318
- cli_section = (
319
- "📋 Prefer the CLI instead?\n"
320
- f" {format_command_hint(run_hint_id) or run_hint_id}\n"
321
- f" {format_command_hint(run_hint_name) or run_hint_name}\n\n"
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
- f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
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\n"
407
- "💡 Use --check-only to just check for updates",
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\n"
429
- "💡 Restart your terminal or run 'aip --version' to verify",
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
- """Resolve the set of sensitive fields to mask based on environment.
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 environment
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 environment configuration. Returns original
140
- rows if masking is disabled or if an error occurs.
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 with AIP_PAGER_WRAP=1 to drop -S.
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
- want_wrap = os.getenv("AIP_PAGER_WRAP", "0") == "1"
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"] = os.getenv("AIP_LESS_FLAGS", default_flags)
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
- v = (os.getenv("AIP_PAGER_HEADER", "1") or "1").strip().lower()
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
- pager_env = (os.getenv("AIP_PAGER", "auto") or "auto").lower()
258
- if pager_env in ("0", "off", "false"):
256
+ pager_mode = (PAGER_MODE or "auto").lower()
257
+ if pager_mode in ("0", "off", "false"):
259
258
  return False
260
- if pager_env in ("1", "on", "true"):
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