glaip-sdk 0.2.2__py3-none-any.whl → 0.4.0__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 (71) hide show
  1. glaip_sdk/cli/auth.py +2 -1
  2. glaip_sdk/cli/commands/agents.py +51 -36
  3. glaip_sdk/cli/commands/configure.py +2 -1
  4. glaip_sdk/cli/commands/mcps.py +219 -62
  5. glaip_sdk/cli/commands/models.py +3 -5
  6. glaip_sdk/cli/commands/tools.py +27 -16
  7. glaip_sdk/cli/commands/transcripts.py +1 -1
  8. glaip_sdk/cli/constants.py +3 -0
  9. glaip_sdk/cli/display.py +1 -1
  10. glaip_sdk/cli/hints.py +58 -0
  11. glaip_sdk/cli/io.py +6 -3
  12. glaip_sdk/cli/main.py +3 -4
  13. glaip_sdk/cli/slash/agent_session.py +4 -13
  14. glaip_sdk/cli/slash/prompt.py +3 -0
  15. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  16. glaip_sdk/cli/slash/session.py +139 -48
  17. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  18. glaip_sdk/cli/slash/tui/remote_runs_app.py +632 -0
  19. glaip_sdk/cli/transcript/capture.py +1 -1
  20. glaip_sdk/cli/transcript/viewer.py +19 -678
  21. glaip_sdk/cli/update_notifier.py +2 -1
  22. glaip_sdk/cli/utils.py +228 -101
  23. glaip_sdk/cli/validators.py +5 -6
  24. glaip_sdk/client/__init__.py +2 -1
  25. glaip_sdk/client/agent_runs.py +147 -0
  26. glaip_sdk/client/agents.py +40 -22
  27. glaip_sdk/client/main.py +2 -6
  28. glaip_sdk/client/mcps.py +13 -5
  29. glaip_sdk/client/run_rendering.py +90 -111
  30. glaip_sdk/client/shared.py +21 -0
  31. glaip_sdk/client/tools.py +2 -3
  32. glaip_sdk/config/constants.py +11 -0
  33. glaip_sdk/models/__init__.py +56 -0
  34. glaip_sdk/models/agent_runs.py +117 -0
  35. glaip_sdk/models.py +8 -7
  36. glaip_sdk/rich_components.py +58 -2
  37. glaip_sdk/utils/client_utils.py +13 -0
  38. glaip_sdk/utils/display.py +23 -15
  39. glaip_sdk/utils/export.py +143 -0
  40. glaip_sdk/utils/import_export.py +6 -9
  41. glaip_sdk/utils/rendering/__init__.py +115 -1
  42. glaip_sdk/utils/rendering/formatting.py +5 -30
  43. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  44. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  45. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  46. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  47. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  48. glaip_sdk/utils/rendering/models.py +1 -0
  49. glaip_sdk/utils/rendering/renderer/__init__.py +10 -28
  50. glaip_sdk/utils/rendering/renderer/base.py +217 -1476
  51. glaip_sdk/utils/rendering/renderer/debug.py +24 -1
  52. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  53. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  54. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  55. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  56. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  57. glaip_sdk/utils/rendering/state.py +204 -0
  58. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  59. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -439
  60. glaip_sdk/utils/rendering/steps/format.py +176 -0
  61. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  62. glaip_sdk/utils/rendering/timing.py +36 -0
  63. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  64. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  65. glaip_sdk/utils/resource_refs.py +26 -15
  66. glaip_sdk/utils/validation.py +13 -21
  67. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/METADATA +24 -2
  68. glaip_sdk-0.4.0.dist-info/RECORD +110 -0
  69. glaip_sdk-0.2.2.dist-info/RECORD +0 -87
  70. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/WHEEL +0 -0
  71. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -4,7 +4,6 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
- # pylint: disable=duplicate-code
8
7
  import json
9
8
  import re
10
9
  from pathlib import Path
@@ -40,7 +39,10 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
40
39
  from glaip_sdk.cli.rich_helpers import markup_text, print_markup
41
40
  from glaip_sdk.cli.utils import (
42
41
  coerce_to_row,
42
+ format_datetime_fields,
43
43
  get_client,
44
+ handle_best_effort_check,
45
+ handle_resource_export,
44
46
  output_list,
45
47
  output_result,
46
48
  spinner_context,
@@ -57,16 +59,32 @@ def tools_group() -> None:
57
59
  pass
58
60
 
59
61
 
60
- # pylint: disable=duplicate-code
61
62
  def _resolve_tool(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
62
- """Resolve tool reference (ID or name) with ambiguity handling."""
63
+ """Resolve a tool by ID or name, handling ambiguous matches interactively.
64
+
65
+ This function provides tool-specific resolution logic. It uses
66
+ resolve_resource_reference to find tools by UUID or name, with interactive
67
+ selection when multiple matches are found.
68
+
69
+ Args:
70
+ ctx: Click context for CLI operations.
71
+ client: API client instance.
72
+ ref: Tool reference (UUID string or name).
73
+ select: Pre-selected index for non-interactive mode (1-based).
74
+
75
+ Returns:
76
+ Tool object if found, None otherwise.
77
+ """
78
+ # Configure tool-specific resolution with standard fuzzy matching
79
+ get_by_id = client.get_tool
80
+ find_by_name = client.find_tools
63
81
  return resolve_resource_reference(
64
82
  ctx,
65
83
  client,
66
84
  ref,
67
85
  "tool",
68
- client.get_tool,
69
- client.find_tools,
86
+ get_by_id,
87
+ find_by_name,
70
88
  "Tool",
71
89
  select=select,
72
90
  )
@@ -100,19 +118,16 @@ def _validate_name_match(provided: str | None, internal: str) -> str:
100
118
 
101
119
  def _check_duplicate_name(client: Any, tool_name: str) -> None:
102
120
  """Raise if a tool with the same name already exists."""
103
- try:
121
+
122
+ def _check_duplicate() -> None:
104
123
  existing = client.find_tools(name=tool_name)
105
124
  if existing:
106
125
  raise click.ClickException(
107
126
  f"A tool named '{tool_name}' already exists. "
108
127
  "Please change your plugin's 'name' to a unique value, then re-run."
109
128
  )
110
- except click.ClickException:
111
- # Re-raise ClickException (intended error)
112
- raise
113
- except Exception:
114
- # Non-fatal: best-effort duplicate check for other errors
115
- pass
129
+
130
+ handle_best_effort_check(_check_duplicate)
116
131
 
117
132
 
118
133
  def _parse_tags(tags: str | None) -> list[str]:
@@ -349,8 +364,6 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
349
364
 
350
365
  # Handle export option
351
366
  if export:
352
- from glaip_sdk.cli.utils import handle_resource_export
353
-
354
367
  handle_resource_export(
355
368
  ctx,
356
369
  tool,
@@ -371,8 +384,6 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
371
384
  if raw_tool_data:
372
385
  # Use raw API data - this preserves ALL fields
373
386
  # Format dates for better display (minimal postprocessing)
374
- from glaip_sdk.cli.utils import format_datetime_fields
375
-
376
387
  formatted_data = format_datetime_fields(raw_tool_data)
377
388
 
378
389
  # Display using output_result with raw data
@@ -37,7 +37,7 @@ from glaip_sdk.cli.transcript.viewer import ViewerContext, run_viewer_session
37
37
  from glaip_sdk.cli.utils import format_size, get_ctx_value, parse_json_line
38
38
  from glaip_sdk.rich_components import AIPTable
39
39
  from glaip_sdk.utils.rendering.renderer.debug import render_debug_event
40
- from glaip_sdk.utils.rendering.renderer.panels import create_final_panel
40
+ from glaip_sdk.utils.rendering.layout.panels import create_final_panel
41
41
 
42
42
  console = Console()
43
43
 
@@ -33,3 +33,6 @@ UPDATE_CHECK_ENABLED = True
33
33
 
34
34
  # Agent instruction preview defaults
35
35
  DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT = 800
36
+
37
+ # Remote runs defaults
38
+ DEFAULT_REMOTE_RUNS_PAGE_LIMIT = 20
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, in_slash_mode
20
+ from glaip_sdk.cli.hints 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
 
glaip_sdk/cli/hints.py ADDED
@@ -0,0 +1,58 @@
1
+ """Helpers for formatting CLI/slash command hints.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ import click
11
+
12
+ from glaip_sdk.branding import HINT_COMMAND_STYLE, HINT_DESCRIPTION_COLOR
13
+
14
+
15
+ def in_slash_mode(ctx: click.Context | None = None) -> bool:
16
+ """Return True when running inside the slash command palette."""
17
+ if ctx is None:
18
+ try:
19
+ ctx = click.get_current_context(silent=True)
20
+ except RuntimeError:
21
+ ctx = None
22
+
23
+ if ctx is None:
24
+ return False
25
+
26
+ obj = getattr(ctx, "obj", None)
27
+ if isinstance(obj, dict):
28
+ return bool(obj.get("_slash_session"))
29
+
30
+ return bool(getattr(obj, "_slash_session", False))
31
+
32
+
33
+ def command_hint(
34
+ cli_command: str | None,
35
+ slash_command: str | None = None,
36
+ *,
37
+ ctx: click.Context | None = None,
38
+ ) -> str | None:
39
+ """Return the appropriate command string for the current mode."""
40
+ if in_slash_mode(ctx):
41
+ if not slash_command:
42
+ return None
43
+ return slash_command if slash_command.startswith("/") else f"/{slash_command}"
44
+
45
+ if not cli_command:
46
+ return None
47
+ return f"aip {cli_command}"
48
+
49
+
50
+ def format_command_hint(command: str | None, description: str | None = None) -> str | None:
51
+ """Return a Rich markup string that highlights a command hint."""
52
+ if not command:
53
+ return None
54
+
55
+ highlighted = f"[{HINT_COMMAND_STYLE}]{command}[/]"
56
+ if description:
57
+ highlighted += f" [{HINT_DESCRIPTION_COLOR}]{description}[/{HINT_DESCRIPTION_COLOR}]"
58
+ return highlighted
glaip_sdk/cli/io.py CHANGED
@@ -7,6 +7,7 @@ Authors:
7
7
  Raymond Christopher (raymond.christopher@gdplabs.id)
8
8
  """
9
9
 
10
+ from importlib import import_module
10
11
  from pathlib import Path
11
12
  from typing import TYPE_CHECKING, Any
12
13
 
@@ -25,9 +26,11 @@ if TYPE_CHECKING: # pragma: no cover - typing-only imports
25
26
 
26
27
  def _create_console() -> "Console":
27
28
  """Return a Console instance (lazy import for easier testing)."""
28
- from rich.console import Console # Local import for test patching
29
-
30
- return Console()
29
+ try:
30
+ console_module = import_module("rich.console")
31
+ except ImportError as exc: # pragma: no cover - optional dependency missing
32
+ raise RuntimeError("Rich Console is not available") from exc
33
+ return console_module.Console()
31
34
 
32
35
 
33
36
  def load_resource_from_file_with_validation(file_path: Path, resource_type: str) -> dict[str, Any]:
glaip_sdk/cli/main.py CHANGED
@@ -34,11 +34,12 @@ from glaip_sdk.cli.commands.mcps import mcps_group
34
34
  from glaip_sdk.cli.commands.models import models_group
35
35
  from glaip_sdk.cli.commands.tools import tools_group
36
36
  from glaip_sdk.cli.commands.transcripts import transcripts_group
37
- from glaip_sdk.cli.commands.update import update_command
37
+ from glaip_sdk.cli.commands.update import _build_upgrade_command, update_command
38
38
  from glaip_sdk.cli.config import load_config
39
39
  from glaip_sdk.cli.transcript import get_transcript_cache_stats
40
40
  from glaip_sdk.cli.update_notifier import maybe_notify_update
41
- from glaip_sdk.cli.utils import format_size, in_slash_mode, sdk_version, spinner_context, update_spinner
41
+ from glaip_sdk.cli.hints import in_slash_mode
42
+ from glaip_sdk.cli.utils import format_size, sdk_version, spinner_context, update_spinner
42
43
  from glaip_sdk.config.constants import (
43
44
  DEFAULT_AGENT_RUN_TIMEOUT,
44
45
  )
@@ -418,8 +419,6 @@ def update(check_only: bool, force: bool) -> None:
418
419
 
419
420
  # Update using pip
420
421
  try:
421
- from glaip_sdk.cli.commands.update import _build_upgrade_command
422
-
423
422
  cmd = list(_build_upgrade_command(include_prerelease=False))
424
423
  # Replace package name with "glaip-sdk" (main.py uses different name)
425
424
  cmd[-1] = "glaip-sdk"
@@ -16,7 +16,8 @@ from glaip_sdk.cli.commands.agents import get as agents_get_command
16
16
  from glaip_sdk.cli.commands.agents import run as agents_run_command
17
17
  from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
18
18
  from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
19
- from glaip_sdk.cli.utils import format_command_hint
19
+ from glaip_sdk.cli.hints import format_command_hint
20
+ from glaip_sdk.cli.utils import bind_slash_session_context
20
21
 
21
22
  if TYPE_CHECKING: # pragma: no cover - type checking only
22
23
  from glaip_sdk.cli.slash.session import SlashSession
@@ -41,6 +42,7 @@ class AgentRunSession:
41
42
  self._contextual_completion_help: dict[str, str] = {
42
43
  "details": "Show this agent's configuration (+ expands prompt).",
43
44
  "help": "Display this context-aware menu.",
45
+ "runs": "✨ NEW · Browse remote run history for this agent.",
44
46
  "exit": "Return to the command palette.",
45
47
  "q": "Return to the command palette.",
46
48
  }
@@ -252,19 +254,8 @@ class AgentRunSession:
252
254
  @contextmanager
253
255
  def _bind_session_context(self) -> Any:
254
256
  """Temporarily attach this slash session to the Click context."""
255
- ctx_obj = getattr(self.session.ctx, "obj", None)
256
- has_context = isinstance(ctx_obj, dict)
257
- previous_session = ctx_obj.get("_slash_session") if has_context else None
258
- if has_context:
259
- ctx_obj["_slash_session"] = self.session
260
- try:
257
+ with bind_slash_session_context(self.session.ctx, self.session):
261
258
  yield
262
- finally:
263
- if has_context:
264
- if previous_session is None:
265
- ctx_obj.pop("_slash_session", None)
266
- else:
267
- ctx_obj["_slash_session"] = previous_session
268
259
 
269
260
  def _run_agent(self, agent_id: str, message: str) -> None:
270
261
  """Execute the agents run command for the active agent."""
@@ -177,8 +177,11 @@ def _iter_command_completions(
177
177
  return []
178
178
 
179
179
  commands = sorted(session._unique_commands.values(), key=lambda c: c.name)
180
+ agent_context = bool(getattr(session, "_current_agent", None))
180
181
 
181
182
  for cmd in commands:
183
+ if getattr(cmd, "agent_only", False) and not agent_context:
184
+ continue
182
185
  yield from _generate_command_completions(cmd, prefix, text, seen)
183
186
 
184
187