glaip-sdk 0.0.19__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.
Files changed (49) hide show
  1. glaip_sdk/_version.py +2 -2
  2. glaip_sdk/branding.py +27 -2
  3. glaip_sdk/cli/auth.py +93 -28
  4. glaip_sdk/cli/commands/__init__.py +2 -2
  5. glaip_sdk/cli/commands/agents.py +108 -21
  6. glaip_sdk/cli/commands/configure.py +141 -90
  7. glaip_sdk/cli/commands/mcps.py +81 -29
  8. glaip_sdk/cli/commands/models.py +4 -3
  9. glaip_sdk/cli/commands/tools.py +27 -14
  10. glaip_sdk/cli/commands/update.py +66 -0
  11. glaip_sdk/cli/config.py +13 -2
  12. glaip_sdk/cli/display.py +35 -26
  13. glaip_sdk/cli/io.py +14 -5
  14. glaip_sdk/cli/main.py +185 -73
  15. glaip_sdk/cli/pager.py +2 -1
  16. glaip_sdk/cli/resolution.py +4 -1
  17. glaip_sdk/cli/slash/__init__.py +3 -4
  18. glaip_sdk/cli/slash/agent_session.py +88 -36
  19. glaip_sdk/cli/slash/prompt.py +20 -48
  20. glaip_sdk/cli/slash/session.py +440 -189
  21. glaip_sdk/cli/transcript/__init__.py +71 -0
  22. glaip_sdk/cli/transcript/cache.py +338 -0
  23. glaip_sdk/cli/transcript/capture.py +278 -0
  24. glaip_sdk/cli/transcript/export.py +38 -0
  25. glaip_sdk/cli/transcript/launcher.py +79 -0
  26. glaip_sdk/cli/transcript/viewer.py +624 -0
  27. glaip_sdk/cli/update_notifier.py +29 -5
  28. glaip_sdk/cli/utils.py +256 -74
  29. glaip_sdk/client/agents.py +3 -1
  30. glaip_sdk/client/run_rendering.py +2 -2
  31. glaip_sdk/icons.py +19 -0
  32. glaip_sdk/models.py +6 -0
  33. glaip_sdk/rich_components.py +29 -1
  34. glaip_sdk/utils/__init__.py +1 -1
  35. glaip_sdk/utils/client_utils.py +6 -4
  36. glaip_sdk/utils/display.py +61 -32
  37. glaip_sdk/utils/rendering/formatting.py +6 -5
  38. glaip_sdk/utils/rendering/renderer/base.py +213 -66
  39. glaip_sdk/utils/rendering/renderer/debug.py +73 -16
  40. glaip_sdk/utils/rendering/renderer/panels.py +27 -15
  41. glaip_sdk/utils/rendering/renderer/progress.py +61 -38
  42. glaip_sdk/utils/serialization.py +5 -2
  43. glaip_sdk/utils/validation.py +1 -2
  44. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.0.20.dist-info}/METADATA +1 -1
  45. glaip_sdk-0.0.20.dist-info/RECORD +80 -0
  46. glaip_sdk/utils/rich_utils.py +0 -29
  47. glaip_sdk-0.0.19.dist-info/RECORD +0 -73
  48. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.0.20.dist-info}/WHEEL +0 -0
  49. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.0.20.dist-info}/entry_points.txt +0 -0
@@ -12,6 +12,13 @@ from typing import Any
12
12
  import click
13
13
  from rich.console import Console
14
14
 
15
+ from glaip_sdk.branding import (
16
+ ACCENT_STYLE,
17
+ ERROR_STYLE,
18
+ INFO,
19
+ SUCCESS_STYLE,
20
+ WARNING_STYLE,
21
+ )
15
22
  from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
16
23
  from glaip_sdk.cli.display import (
17
24
  display_api_error,
@@ -40,6 +47,7 @@ from glaip_sdk.cli.utils import (
40
47
  output_result,
41
48
  spinner_context,
42
49
  )
50
+ from glaip_sdk.icons import ICON_TOOL
43
51
  from glaip_sdk.utils import format_datetime
44
52
  from glaip_sdk.utils.import_export import merge_import_with_cli_args
45
53
 
@@ -202,8 +210,8 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
202
210
  # Define table columns: (data_key, header, style, width)
203
211
  columns = [
204
212
  ("id", "ID", "dim", 36),
205
- ("name", "Name", "cyan", None),
206
- ("framework", "Framework", "blue", None),
213
+ ("name", "Name", ACCENT_STYLE, None),
214
+ ("framework", "Framework", INFO, None),
207
215
  ]
208
216
 
209
217
  # Transform function for safe dictionary access
@@ -213,7 +221,7 @@ def list_tools(ctx: Any, tool_type: str | None) -> None:
213
221
  row["id"] = str(row["id"])
214
222
  return row
215
223
 
216
- output_list(ctx, tools, "🔧 Available Tools", columns, transform_tool)
224
+ output_list(ctx, tools, f"{ICON_TOOL} Available Tools", columns, transform_tool)
217
225
 
218
226
  except Exception as e:
219
227
  raise click.ClickException(str(e))
@@ -350,11 +358,11 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
350
358
  tool = client.get_tool_by_id(tool.id)
351
359
  except Exception as e:
352
360
  print_markup(
353
- f"[yellow]⚠️ Could not fetch full tool details: {e}[/yellow]",
361
+ f"[{WARNING_STYLE}]⚠️ Could not fetch full tool details: {e}[/]",
354
362
  console=console,
355
363
  )
356
364
  print_markup(
357
- "[yellow]⚠️ Proceeding with available data[/yellow]",
365
+ f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]",
358
366
  console=console,
359
367
  )
360
368
 
@@ -365,7 +373,7 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
365
373
  ):
366
374
  export_resource_to_file(tool, export_path, detected_format)
367
375
  print_markup(
368
- f"[green]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/green]",
376
+ f"[{SUCCESS_STYLE}]✅ Complete tool configuration exported to: {export_path} (format: {detected_format})[/]",
369
377
  console=console,
370
378
  )
371
379
 
@@ -395,11 +403,11 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
395
403
  ctx,
396
404
  formatted_data,
397
405
  title="Tool Details",
398
- panel_title=f"🔧 {raw_tool_data.get('name', 'Unknown')}",
406
+ panel_title=f"{ICON_TOOL} {raw_tool_data.get('name', 'Unknown')}",
399
407
  )
400
408
  else:
401
409
  # Fall back to original method if raw fetch fails
402
- console.print("[yellow]Falling back to Pydantic model data[/yellow]")
410
+ console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
403
411
 
404
412
  # Create result data with all available fields from backend
405
413
  result_data = {
@@ -412,7 +420,10 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
412
420
  }
413
421
 
414
422
  output_result(
415
- ctx, result_data, title="Tool Details", panel_title=f"🔧 {tool.name}"
423
+ ctx,
424
+ result_data,
425
+ title="Tool Details",
426
+ panel_title=f"{ICON_TOOL} {tool.name}",
416
427
  )
417
428
 
418
429
  except Exception as e:
@@ -475,7 +486,7 @@ def update(
475
486
  )
476
487
  handle_rich_output(
477
488
  ctx,
478
- markup_text(f"[green]✓[/green] Tool code updated from {file}"),
489
+ markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool code updated from {file}"),
479
490
  )
480
491
  elif update_data:
481
492
  # Update metadata only (native tools only)
@@ -490,11 +501,11 @@ def update(
490
501
  ):
491
502
  updated_tool = tool.update(**update_data)
492
503
  handle_rich_output(
493
- ctx, markup_text("[green]✓[/green] Tool metadata updated")
504
+ ctx, markup_text(f"[{SUCCESS_STYLE}]✓[/] Tool metadata updated")
494
505
  )
495
506
  else:
496
507
  handle_rich_output(
497
- ctx, markup_text("[yellow]No updates specified[/yellow]")
508
+ ctx, markup_text(f"[{WARNING_STYLE}]No updates specified[/]")
498
509
  )
499
510
  return
500
511
 
@@ -574,11 +585,13 @@ def script(ctx: Any, tool_id: str) -> None:
574
585
  if get_ctx_value(ctx, "view") == "json":
575
586
  click.echo(json.dumps({"script": script_content}, indent=2))
576
587
  else:
577
- console.print(f"[green]📜 Tool Script for '{tool_id}':[/green]")
588
+ console.print(f"[{SUCCESS_STYLE}]📜 Tool Script for '{tool_id}':[/]")
578
589
  console.print(script_content)
579
590
 
580
591
  except Exception as e:
581
592
  handle_json_output(ctx, error=e)
582
593
  if get_ctx_value(ctx, "view") != "json":
583
- print_markup(f"[red]Error getting tool script: {e}[/red]", console=console)
594
+ print_markup(
595
+ f"[{ERROR_STYLE}]Error getting tool script: {e}[/]", console=console
596
+ )
584
597
  raise click.ClickException(str(e))
@@ -0,0 +1,66 @@
1
+ """Update command for upgrading the glaip-sdk package.
2
+
3
+ Author:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import subprocess
10
+ import sys
11
+ from collections.abc import Sequence
12
+
13
+ import click
14
+ from rich.console import Console
15
+
16
+ from glaip_sdk.branding import ACCENT_STYLE, ERROR_STYLE, INFO_STYLE, SUCCESS_STYLE
17
+
18
+ PACKAGE_NAME = "glaip-sdk"
19
+
20
+
21
+ def _build_upgrade_command(include_prerelease: bool) -> Sequence[str]:
22
+ """Return the pip command used to upgrade the SDK."""
23
+ command = [
24
+ sys.executable,
25
+ "-m",
26
+ "pip",
27
+ "install",
28
+ "--upgrade",
29
+ PACKAGE_NAME,
30
+ ]
31
+ if include_prerelease:
32
+ command.append("--pre")
33
+ return command
34
+
35
+
36
+ @click.command(name="update")
37
+ @click.option(
38
+ "--pre",
39
+ "include_prerelease",
40
+ is_flag=True,
41
+ help="Include pre-release versions when upgrading.",
42
+ )
43
+ def update_command(include_prerelease: bool) -> None:
44
+ """Upgrade the glaip-sdk package using pip."""
45
+ console = Console()
46
+ upgrade_cmd = _build_upgrade_command(include_prerelease)
47
+ console.print(
48
+ f"[{ACCENT_STYLE}]Upgrading {PACKAGE_NAME} using[/] "
49
+ f"[{INFO_STYLE}]{' '.join(upgrade_cmd)}[/]"
50
+ )
51
+
52
+ try:
53
+ subprocess.run(upgrade_cmd, check=True)
54
+ except FileNotFoundError as exc:
55
+ raise click.ClickException(
56
+ "Unable to locate Python executable to run pip. "
57
+ "Please ensure Python is installed and try again."
58
+ ) from exc
59
+ except subprocess.CalledProcessError as exc:
60
+ console.print(
61
+ f"[{ERROR_STYLE}]Automatic upgrade failed.[/] "
62
+ f"Please run `pip install -U {PACKAGE_NAME}` manually."
63
+ )
64
+ raise click.ClickException("Automatic upgrade failed.") from exc
65
+
66
+ console.print(f"[{SUCCESS_STYLE}]✅ {PACKAGE_NAME} upgraded successfully.[/]")
glaip_sdk/cli/config.py CHANGED
@@ -12,6 +12,14 @@ import yaml
12
12
 
13
13
  CONFIG_DIR = Path.home() / ".aip"
14
14
  CONFIG_FILE = CONFIG_DIR / "config.yaml"
15
+ _ALLOWED_KEYS = {"api_url", "api_key", "timeout"}
16
+
17
+
18
+ def _sanitize_config(data: dict[str, Any] | None) -> dict[str, Any]:
19
+ """Return config filtered to allowed keys only."""
20
+ if not data:
21
+ return {}
22
+ return {k: v for k, v in data.items() if k in _ALLOWED_KEYS}
15
23
 
16
24
 
17
25
  def load_config() -> dict[str, Any]:
@@ -21,7 +29,8 @@ def load_config() -> dict[str, Any]:
21
29
 
22
30
  try:
23
31
  with open(CONFIG_FILE) as f:
24
- return yaml.safe_load(f) or {}
32
+ loaded = yaml.safe_load(f) or {}
33
+ return _sanitize_config(loaded)
25
34
  except yaml.YAMLError:
26
35
  return {}
27
36
 
@@ -30,8 +39,10 @@ def save_config(config: dict[str, Any]) -> None:
30
39
  """Save configuration to file."""
31
40
  CONFIG_DIR.mkdir(exist_ok=True)
32
41
 
42
+ sanitized = _sanitize_config(config)
43
+
33
44
  with open(CONFIG_FILE, "w") as f:
34
- yaml.dump(config, f, default_flow_style=False)
45
+ yaml.dump(sanitized, f, default_flow_style=False)
35
46
 
36
47
  # Set secure file permissions
37
48
  try:
glaip_sdk/cli/display.py CHANGED
@@ -15,8 +15,10 @@ 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
+ from glaip_sdk.branding import ERROR_STYLE, SUCCESS, SUCCESS_STYLE, WARNING_STYLE
19
+ from glaip_sdk.cli.rich_helpers import markup_text
20
+ from glaip_sdk.cli.utils import command_hint, format_command_hint
21
+ from glaip_sdk.icons import ICON_AGENT, ICON_TOOL
20
22
  from glaip_sdk.rich_components import AIPPanel
21
23
 
22
24
  console = Console()
@@ -44,10 +46,10 @@ def display_creation_success(
44
46
  )
45
47
 
46
48
  return AIPPanel(
47
- f"[green]✅ {resource_type} '{resource_name}' created successfully![/green]\n\n"
49
+ f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' created successfully![/]\n\n"
48
50
  f"ID: {resource_id}{fields_display}",
49
- title=f"🤖 {resource_type} Created",
50
- border_style="green",
51
+ title=f"{ICON_AGENT} {resource_type} Created",
52
+ border_style=SUCCESS,
51
53
  padding=(0, 1),
52
54
  )
53
55
 
@@ -63,7 +65,7 @@ def display_update_success(resource_type: str, resource_name: str) -> Text:
63
65
  Rich Text object for display
64
66
  """
65
67
  return markup_text(
66
- f"[green]✅ {resource_type} '{resource_name}' updated successfully[/green]"
68
+ f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' updated successfully[/]"
67
69
  )
68
70
 
69
71
 
@@ -78,7 +80,7 @@ def display_deletion_success(resource_type: str, resource_name: str) -> Text:
78
80
  Rich Text object for display
79
81
  """
80
82
  return markup_text(
81
- f"[green]✅ {resource_type} '{resource_name}' deleted successfully[/green]"
83
+ f"[{SUCCESS_STYLE}]✅ {resource_type} '{resource_name}' deleted successfully[/]"
82
84
  )
83
85
 
84
86
 
@@ -90,8 +92,15 @@ def display_api_error(error: Exception, operation: str = "operation") -> None:
90
92
  operation: Description of the operation that failed
91
93
  """
92
94
  error_type = type(error).__name__
93
- print_markup(f"[red]Error during {operation}: {error}[/red]", console=console)
94
- print_markup(f"[dim]Error type: {error_type}[/dim]", console=console)
95
+ error_message = markup_text(f"[{ERROR_STYLE}]Error during {operation}: {error}[/]")
96
+ error_message.no_wrap = True
97
+ error_message.overflow = "ignore"
98
+ console.print(error_message)
99
+
100
+ error_type_message = markup_text(f"[dim]Error type: {error_type}[/dim]")
101
+ error_type_message.no_wrap = True
102
+ error_type_message.overflow = "ignore"
103
+ console.print(error_type_message)
95
104
 
96
105
 
97
106
  def print_api_error(e: Exception) -> None:
@@ -107,17 +116,17 @@ def print_api_error(e: Exception) -> None:
107
116
  - Special handling for validation errors with detailed field-level errors
108
117
  """
109
118
  if not hasattr(e, "__dict__"):
110
- console.print(f"[red]Error: {e}[/red]")
119
+ console.print(f"[{ERROR_STYLE}]Error: {e}[/]")
111
120
  return
112
121
 
113
122
  if not hasattr(e, "status_code"):
114
- console.print(f"[red]Error: {e}[/red]")
123
+ console.print(f"[{ERROR_STYLE}]Error: {e}[/]")
115
124
  return
116
125
 
117
- console.print(f"[red]API Error: {e}[/red]")
126
+ console.print(f"[{ERROR_STYLE}]API Error: {e}[/]")
118
127
  status_code = getattr(e, "status_code", None)
119
128
  if status_code is not None:
120
- console.print(f"[yellow]Status: {status_code}[/yellow]")
129
+ console.print(f"[{WARNING_STYLE}]Status: {status_code}[/]")
121
130
 
122
131
  payload = getattr(e, "payload", _MISSING)
123
132
  if payload is _MISSING:
@@ -125,9 +134,9 @@ def print_api_error(e: Exception) -> None:
125
134
 
126
135
  if payload:
127
136
  if not _print_structured_payload(payload):
128
- console.print(f"[yellow]Details: {payload}[/yellow]")
137
+ console.print(f"[{WARNING_STYLE}]Details: {payload}[/]")
129
138
  else:
130
- console.print(f"[yellow]Details: {payload}[/yellow]")
139
+ console.print(f"[{WARNING_STYLE}]Details: {payload}[/]")
131
140
 
132
141
 
133
142
  def _print_structured_payload(payload: Any) -> bool:
@@ -149,18 +158,18 @@ def _print_validation_details(detail: Any) -> bool:
149
158
  if not isinstance(detail, list) or not detail:
150
159
  return False
151
160
 
152
- console.print("[red]Validation Errors:[/red]")
161
+ console.print(f"[{ERROR_STYLE}]Validation Errors:[/]")
153
162
  for error in detail:
154
163
  if isinstance(error, dict):
155
164
  loc = " -> ".join(str(x) for x in error.get("loc", []))
156
165
  msg = error.get("msg", "Unknown error")
157
166
  error_type = error.get("type", "unknown")
158
167
  location = loc if loc else "field"
159
- console.print(f" [yellow]• {location}:[/yellow] {msg}")
168
+ console.print(f" [{WARNING_STYLE}]• {location}:[/] {msg}")
160
169
  if error_type != "unknown":
161
170
  console.print(f" [dim]({error_type})[/dim]")
162
171
  else:
163
- console.print(f" [yellow]•[/yellow] {error}")
172
+ console.print(f" [{WARNING_STYLE}]•[/] {error}")
164
173
  return True
165
174
 
166
175
 
@@ -169,14 +178,14 @@ def _print_details_field(details: Any) -> bool:
169
178
  if not details:
170
179
  return False
171
180
 
172
- console.print("[red]Error Details:[/red]")
181
+ console.print(f"[{ERROR_STYLE}]Error Details:[/]")
173
182
  if isinstance(details, str):
174
- console.print(f" [yellow]•[/yellow] {details}")
183
+ console.print(f" [{WARNING_STYLE}]•[/] {details}")
175
184
  elif isinstance(details, list):
176
185
  for detail in details:
177
- console.print(f" [yellow]•[/yellow] {detail}")
186
+ console.print(f" [{WARNING_STYLE}]•[/] {detail}")
178
187
  else:
179
- console.print(f" [yellow]•[/yellow] {details}")
188
+ console.print(f" [{WARNING_STYLE}]•[/] {details}")
180
189
  return True
181
190
 
182
191
 
@@ -310,8 +319,8 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
310
319
  if run_hint_id and run_hint_name:
311
320
  cli_section = (
312
321
  "📋 Prefer the CLI instead?\n"
313
- f" [green]{run_hint_id}[/green]\n"
314
- f" [green]{run_hint_name}[/green]\n\n"
322
+ f" {format_command_hint(run_hint_id) or run_hint_id}\n"
323
+ f" {format_command_hint(run_hint_name) or run_hint_name}\n\n"
315
324
  )
316
325
 
317
326
  return AIPPanel(
@@ -319,7 +328,7 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
319
328
  f"🚀 Start chatting with [bold]{agent_name}[/bold] right here:\n"
320
329
  f" Type your message below and press Enter to run it immediately.\n\n"
321
330
  f"{cli_section}"
322
- f"🔧 Available options:\n"
331
+ f"{ICON_TOOL} Available options:\n"
323
332
  f" [dim]--chat-history[/dim] Include previous conversation\n"
324
333
  f" [dim]--file[/dim] Attach files\n"
325
334
  f" [dim]--input[/dim] Alternative input method\n"
@@ -327,7 +336,7 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
327
336
  f" [dim]--save[/dim] Save transcript to file\n"
328
337
  f" [dim]--verbose[/dim] Show detailed execution\n\n"
329
338
  f"💡 [dim]Input text can be positional OR use --input flag (both work!)[/dim]",
330
- title="🤖 Ready to Run Agent",
339
+ title=f"{ICON_AGENT} Ready to Run Agent",
331
340
  border_style="blue",
332
341
  padding=(0, 1),
333
342
  )
glaip_sdk/cli/io.py CHANGED
@@ -8,16 +8,27 @@ Authors:
8
8
  """
9
9
 
10
10
  from pathlib import Path
11
- from typing import Any
11
+ from typing import TYPE_CHECKING, Any
12
12
 
13
13
  import click
14
14
 
15
+ from glaip_sdk.branding import WARNING_STYLE
15
16
  from glaip_sdk.utils.serialization import (
16
17
  collect_attributes_for_export,
17
18
  load_resource_from_file,
18
19
  write_resource_export,
19
20
  )
20
21
 
22
+ if TYPE_CHECKING: # pragma: no cover - typing-only imports
23
+ from rich.console import Console
24
+
25
+
26
+ def _create_console() -> "Console":
27
+ """Return a Console instance (lazy import for easier testing)."""
28
+ from rich.console import Console # Local import for test patching
29
+
30
+ return Console()
31
+
21
32
 
22
33
  def load_resource_from_file_with_validation(
23
34
  file_path: Path, resource_type: str
@@ -79,9 +90,7 @@ def fetch_raw_resource_details(client: Any, resource: Any, resource_type: str) -
79
90
  Notes:
80
91
  This is CLI-specific functionality for displaying comprehensive resource details.
81
92
  """
82
- from rich.console import Console
83
-
84
- console = Console()
93
+ console = _create_console()
85
94
 
86
95
  try:
87
96
  resource_id = str(getattr(resource, "id", "")).strip()
@@ -99,7 +108,7 @@ def fetch_raw_resource_details(client: Any, resource: Any, resource_type: str) -
99
108
  return raw_response
100
109
  except Exception as e:
101
110
  console.print(
102
- f"[yellow]Failed to fetch raw {resource_type} details: {e}[/yellow]"
111
+ f"[{WARNING_STYLE}]Failed to fetch raw {resource_type} details: {e}[/]"
103
112
  )
104
113
  # Fall back to regular method
105
114
  return None