glaip-sdk 0.6.15b2__py3-none-any.whl ā 0.6.16__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/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1196 -0
- glaip_sdk/cli/__init__.py +9 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +78 -0
- glaip_sdk/cli/auth.py +699 -0
- glaip_sdk/cli/commands/__init__.py +5 -0
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +1509 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +896 -0
- glaip_sdk/cli/commands/mcps.py +1356 -0
- glaip_sdk/cli/commands/models.py +69 -0
- glaip_sdk/cli/commands/tools.py +576 -0
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +61 -0
- glaip_sdk/cli/config.py +95 -0
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +150 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +355 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +112 -0
- glaip_sdk/cli/main.py +615 -0
- glaip_sdk/cli/masking.py +136 -0
- glaip_sdk/cli/mcp_validators.py +287 -0
- glaip_sdk/cli/pager.py +266 -0
- glaip_sdk/cli/parsers/__init__.py +7 -0
- glaip_sdk/cli/parsers/json_input.py +177 -0
- glaip_sdk/cli/resolution.py +67 -0
- glaip_sdk/cli/rich_helpers.py +27 -0
- glaip_sdk/cli/slash/__init__.py +15 -0
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +285 -0
- glaip_sdk/cli/slash/prompt.py +256 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +1708 -0
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +31 -0
- glaip_sdk/cli/transcript/cache.py +536 -0
- glaip_sdk/cli/transcript/capture.py +329 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +77 -0
- glaip_sdk/cli/transcript/viewer.py +374 -0
- glaip_sdk/cli/update_notifier.py +290 -0
- glaip_sdk/cli/utils.py +263 -0
- glaip_sdk/cli/validators.py +238 -0
- glaip_sdk/client/__init__.py +11 -0
- glaip_sdk/client/_agent_payloads.py +520 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +1335 -0
- glaip_sdk/client/base.py +502 -0
- glaip_sdk/client/main.py +249 -0
- glaip_sdk/client/mcps.py +370 -0
- glaip_sdk/client/run_rendering.py +700 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +661 -0
- glaip_sdk/client/validators.py +198 -0
- glaip_sdk/config/constants.py +52 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +7 -0
- glaip_sdk/payload_schemas/agent.py +85 -0
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +782 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +86 -0
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +194 -0
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +486 -0
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +135 -0
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +61 -0
- glaip_sdk/utils/import_export.py +168 -0
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -0
- glaip_sdk/utils/rendering/formatting.py +264 -0
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/layout/panels.py +156 -0
- glaip_sdk/utils/rendering/layout/progress.py +202 -0
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +85 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +55 -0
- glaip_sdk/utils/rendering/renderer/base.py +1024 -0
- glaip_sdk/utils/rendering/renderer/config.py +27 -0
- glaip_sdk/utils/rendering/renderer/console.py +55 -0
- glaip_sdk/utils/rendering/renderer/debug.py +178 -0
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +202 -0
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +195 -0
- glaip_sdk/utils/run_renderer.py +41 -0
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +424 -0
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +264 -0
- {glaip_sdk-0.6.15b2.dist-info ā glaip_sdk-0.6.16.dist-info}/METADATA +4 -5
- glaip_sdk-0.6.16.dist-info/RECORD +160 -0
- glaip_sdk-0.6.15b2.dist-info/RECORD +0 -12
- {glaip_sdk-0.6.15b2.dist-info ā glaip_sdk-0.6.16.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.15b2.dist-info ā glaip_sdk-0.6.16.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.6.15b2.dist-info ā glaip_sdk-0.6.16.dist-info}/top_level.txt +0 -0
glaip_sdk/cli/main.py
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
"""Main CLI entry point for AIP SDK.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from glaip_sdk.branding import (
|
|
15
|
+
ERROR,
|
|
16
|
+
ERROR_STYLE,
|
|
17
|
+
INFO,
|
|
18
|
+
INFO_STYLE,
|
|
19
|
+
NEUTRAL,
|
|
20
|
+
SUCCESS,
|
|
21
|
+
SUCCESS_STYLE,
|
|
22
|
+
WARNING,
|
|
23
|
+
WARNING_STYLE,
|
|
24
|
+
AIPBranding,
|
|
25
|
+
)
|
|
26
|
+
from glaip_sdk.cli.account_store import get_account_store
|
|
27
|
+
from glaip_sdk.cli.auth import resolve_credentials
|
|
28
|
+
from glaip_sdk.cli.commands.accounts import accounts_group
|
|
29
|
+
from glaip_sdk.cli.commands.agents import agents_group
|
|
30
|
+
from glaip_sdk.cli.commands.configure import (
|
|
31
|
+
config_group,
|
|
32
|
+
configure_command,
|
|
33
|
+
)
|
|
34
|
+
from glaip_sdk.cli.commands.mcps import mcps_group
|
|
35
|
+
from glaip_sdk.cli.commands.models import models_group
|
|
36
|
+
from glaip_sdk.cli.commands.tools import tools_group
|
|
37
|
+
from glaip_sdk.cli.commands.transcripts import transcripts_group
|
|
38
|
+
from glaip_sdk.cli.commands.update import _build_upgrade_command, update_command
|
|
39
|
+
from glaip_sdk.cli.config import load_config
|
|
40
|
+
from glaip_sdk.cli.hints import in_slash_mode
|
|
41
|
+
from glaip_sdk.cli.transcript import get_transcript_cache_stats
|
|
42
|
+
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
43
|
+
from glaip_sdk.cli.utils import format_size, sdk_version, spinner_context, update_spinner
|
|
44
|
+
from glaip_sdk.config.constants import (
|
|
45
|
+
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
46
|
+
)
|
|
47
|
+
from glaip_sdk.icons import ICON_AGENT
|
|
48
|
+
from glaip_sdk.rich_components import AIPPanel, AIPTable
|
|
49
|
+
|
|
50
|
+
Client: type[Any] | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _resolve_client_class() -> type[Any]:
|
|
54
|
+
"""Resolve the Client class lazily to avoid heavy imports at CLI startup."""
|
|
55
|
+
global Client
|
|
56
|
+
if Client is None:
|
|
57
|
+
from glaip_sdk import Client as ClientClass # noqa: PLC0415
|
|
58
|
+
|
|
59
|
+
Client = ClientClass
|
|
60
|
+
return Client
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _suppress_chatty_loggers() -> None:
|
|
64
|
+
"""Silence noisy SDK/httpx logs for CLI output."""
|
|
65
|
+
noisy_loggers = [
|
|
66
|
+
"glaip_sdk.client",
|
|
67
|
+
"httpx",
|
|
68
|
+
"httpcore",
|
|
69
|
+
]
|
|
70
|
+
for name in noisy_loggers:
|
|
71
|
+
logger = logging.getLogger(name)
|
|
72
|
+
# Respect existing configuration: only raise level when unset,
|
|
73
|
+
# and avoid changing propagation if a custom handler is already attached.
|
|
74
|
+
if logger.level == logging.NOTSET:
|
|
75
|
+
logger.setLevel(logging.WARNING)
|
|
76
|
+
if not logger.handlers:
|
|
77
|
+
logger.propagate = False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Import SlashSession for potential mocking in tests
|
|
81
|
+
try:
|
|
82
|
+
from glaip_sdk.cli.slash import SlashSession
|
|
83
|
+
except ImportError: # pragma: no cover - optional slash dependencies
|
|
84
|
+
# Slash dependencies might not be available in all environments
|
|
85
|
+
SlashSession = None
|
|
86
|
+
|
|
87
|
+
# Constants
|
|
88
|
+
AVAILABLE_STATUS = "ā
Available"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@click.group(invoke_without_command=True)
|
|
92
|
+
@click.version_option(package_name="glaip-sdk", prog_name="aip")
|
|
93
|
+
@click.option(
|
|
94
|
+
"--api-url",
|
|
95
|
+
help="(Deprecated) AIP API URL; use profiles via --account instead",
|
|
96
|
+
hidden=True,
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--api-key",
|
|
100
|
+
help="(Deprecated) AIP API Key; use profiles via --account instead",
|
|
101
|
+
hidden=True,
|
|
102
|
+
)
|
|
103
|
+
@click.option("--timeout", default=30.0, help="Request timeout in seconds")
|
|
104
|
+
@click.option(
|
|
105
|
+
"--view",
|
|
106
|
+
"view",
|
|
107
|
+
type=click.Choice(["rich", "plain", "json", "md"]),
|
|
108
|
+
default="rich",
|
|
109
|
+
help="Output view format",
|
|
110
|
+
)
|
|
111
|
+
@click.option("--no-tty", is_flag=True, help="Disable TTY renderer")
|
|
112
|
+
@click.option(
|
|
113
|
+
"--account",
|
|
114
|
+
"account_name",
|
|
115
|
+
help="Target a named account profile for this command",
|
|
116
|
+
hidden=True, # Hidden by default, shown with --help --all
|
|
117
|
+
)
|
|
118
|
+
@click.pass_context
|
|
119
|
+
def main(
|
|
120
|
+
ctx: Any,
|
|
121
|
+
api_url: str | None,
|
|
122
|
+
api_key: str | None,
|
|
123
|
+
timeout: float | None,
|
|
124
|
+
view: str | None,
|
|
125
|
+
no_tty: bool,
|
|
126
|
+
account_name: str | None,
|
|
127
|
+
) -> None:
|
|
128
|
+
r"""GL AIP SDK Command Line Interface.
|
|
129
|
+
|
|
130
|
+
A comprehensive CLI for managing GL AIP resources including
|
|
131
|
+
agents, tools, MCPs, and more.
|
|
132
|
+
|
|
133
|
+
\b
|
|
134
|
+
Examples:
|
|
135
|
+
aip version # Show detailed version info
|
|
136
|
+
aip configure # Configure credentials
|
|
137
|
+
aip accounts add prod # Add account profile
|
|
138
|
+
aip accounts use staging # Switch account
|
|
139
|
+
aip agents list # List all agents
|
|
140
|
+
aip tools create my_tool.py # Create a new tool
|
|
141
|
+
aip agents run my-agent "Hello world" # Run an agent
|
|
142
|
+
|
|
143
|
+
\b
|
|
144
|
+
NEW: Store multiple accounts via 'aip accounts add' and switch with 'aip accounts use'.
|
|
145
|
+
"""
|
|
146
|
+
# Store configuration in context
|
|
147
|
+
ctx.ensure_object(dict)
|
|
148
|
+
ctx.obj["api_url"] = api_url
|
|
149
|
+
ctx.obj["api_key"] = api_key
|
|
150
|
+
ctx.obj["timeout"] = timeout
|
|
151
|
+
ctx.obj["view"] = view
|
|
152
|
+
ctx.obj["account_name"] = account_name
|
|
153
|
+
|
|
154
|
+
_suppress_chatty_loggers()
|
|
155
|
+
|
|
156
|
+
ctx.obj["tty"] = not no_tty
|
|
157
|
+
|
|
158
|
+
launching_slash = (
|
|
159
|
+
ctx.invoked_subcommand is None
|
|
160
|
+
and not ctx.resilient_parsing
|
|
161
|
+
and _should_launch_slash(ctx)
|
|
162
|
+
and SlashSession is not None
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if not ctx.resilient_parsing and ctx.obj["tty"] and not launching_slash:
|
|
166
|
+
console = Console()
|
|
167
|
+
preferred_console = maybe_notify_update(
|
|
168
|
+
sdk_version(),
|
|
169
|
+
console=console,
|
|
170
|
+
ctx=ctx,
|
|
171
|
+
slash_command="update",
|
|
172
|
+
)
|
|
173
|
+
ctx.obj["_preferred_console"] = preferred_console or console
|
|
174
|
+
|
|
175
|
+
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
176
|
+
if launching_slash:
|
|
177
|
+
session = SlashSession(ctx)
|
|
178
|
+
session.run()
|
|
179
|
+
ctx.exit()
|
|
180
|
+
else:
|
|
181
|
+
click.echo(ctx.get_help())
|
|
182
|
+
ctx.exit()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Add command groups
|
|
186
|
+
main.add_command(accounts_group)
|
|
187
|
+
main.add_command(agents_group)
|
|
188
|
+
main.add_command(config_group)
|
|
189
|
+
main.add_command(tools_group)
|
|
190
|
+
main.add_command(mcps_group)
|
|
191
|
+
main.add_command(models_group)
|
|
192
|
+
main.add_command(transcripts_group)
|
|
193
|
+
|
|
194
|
+
# Add top-level commands
|
|
195
|
+
main.add_command(configure_command)
|
|
196
|
+
main.add_command(update_command)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Tip: `--version` is provided by click.version_option above.
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _should_launch_slash(ctx: click.Context) -> bool:
|
|
203
|
+
"""Determine whether to open the command palette automatically."""
|
|
204
|
+
ctx_obj = ctx.obj or {}
|
|
205
|
+
if not bool(ctx_obj.get("tty", True)):
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _load_and_merge_config(ctx: click.Context) -> dict:
|
|
215
|
+
"""Load configuration from multiple sources and merge them."""
|
|
216
|
+
context_config = ctx.obj or {}
|
|
217
|
+
account_name = context_config.get("account_name")
|
|
218
|
+
|
|
219
|
+
# Resolve credentials using new account store system
|
|
220
|
+
api_url, api_key, source = resolve_credentials(
|
|
221
|
+
account_name=account_name,
|
|
222
|
+
api_url=context_config.get("api_url"),
|
|
223
|
+
api_key=context_config.get("api_key"),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Load other config values (timeout, etc.) from legacy config
|
|
227
|
+
legacy_config = load_config()
|
|
228
|
+
timeout = context_config.get("timeout") or legacy_config.get("timeout")
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"api_url": api_url,
|
|
232
|
+
"api_key": api_key,
|
|
233
|
+
"timeout": timeout,
|
|
234
|
+
"_source": source, # Track where credentials came from
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _validate_config_and_show_error(config: dict, console: Console) -> None:
|
|
239
|
+
"""Validate configuration and show error if incomplete."""
|
|
240
|
+
store = get_account_store()
|
|
241
|
+
has_accounts = bool(store.list_accounts())
|
|
242
|
+
if not config.get("api_url") or not config.get("api_key"):
|
|
243
|
+
no_accounts_hint = "" if has_accounts else "\n ⢠No accounts found; create one now to continue"
|
|
244
|
+
console.print(
|
|
245
|
+
AIPPanel(
|
|
246
|
+
f"[{ERROR_STYLE}]ā Configuration incomplete[/]\n\n"
|
|
247
|
+
f"š Current config:\n"
|
|
248
|
+
f" ⢠API URL: {config.get('api_url', 'Not set')}\n"
|
|
249
|
+
f" ⢠API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
|
|
250
|
+
f"š” To fix this:\n"
|
|
251
|
+
f" ⢠Run 'aip accounts add default' to set up credentials\n"
|
|
252
|
+
f" ⢠Or run 'aip configure' for interactive setup\n"
|
|
253
|
+
f" ⢠Or run 'aip accounts list' to see current accounts{no_accounts_hint}",
|
|
254
|
+
title="ā Configuration Error",
|
|
255
|
+
border_style=ERROR,
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
console.print(f"\n[{SUCCESS_STYLE}]ā
AIP - Ready[/] (SDK v{sdk_version()}) - Configure to connect")
|
|
259
|
+
sys.exit(1)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _resolve_status_console(ctx: Any) -> tuple[Console, bool]:
|
|
263
|
+
"""Return the console to use and whether we are in slash mode."""
|
|
264
|
+
ctx_obj = ctx.obj if isinstance(ctx.obj, dict) else None
|
|
265
|
+
console_override = ctx_obj.get("_slash_console") if ctx_obj else None
|
|
266
|
+
preferred_console = ctx_obj.get("_preferred_console") if ctx_obj else None
|
|
267
|
+
if preferred_console is None:
|
|
268
|
+
# In heavily mocked tests, maybe_notify_update may be patched with a return_value
|
|
269
|
+
preferred_console = getattr(maybe_notify_update, "return_value", None)
|
|
270
|
+
console = console_override or preferred_console or Console()
|
|
271
|
+
slash_mode = in_slash_mode(ctx)
|
|
272
|
+
return console, slash_mode
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _render_status_heading(console: Console, slash_mode: bool, config: dict) -> bool:
|
|
276
|
+
"""Print the status heading/banner.
|
|
277
|
+
|
|
278
|
+
Returns True if a generic ready line was printed (to avoid duplication).
|
|
279
|
+
"""
|
|
280
|
+
del slash_mode # heading now consistent across invocation contexts
|
|
281
|
+
ready_printed = False
|
|
282
|
+
console.print(f"[{INFO_STYLE}]GL AIP status[/]")
|
|
283
|
+
console.print("")
|
|
284
|
+
|
|
285
|
+
# Show account information
|
|
286
|
+
source = str(config.get("_source") or "unknown")
|
|
287
|
+
account_name = None
|
|
288
|
+
if source.startswith("account:") or source.startswith("active_profile:"):
|
|
289
|
+
account_name = source.split(":", 1)[1]
|
|
290
|
+
|
|
291
|
+
if account_name:
|
|
292
|
+
store = get_account_store()
|
|
293
|
+
account = store.get_account(account_name)
|
|
294
|
+
if account:
|
|
295
|
+
url = account.get("api_url", "")
|
|
296
|
+
# Format source to match spec: "active_profile" instead of "active_profile:name"
|
|
297
|
+
display_source = source.split(":")[0] if ":" in source else source
|
|
298
|
+
console.print(f"[{SUCCESS_STYLE}]Account: {account_name} (source={display_source}) Ā· API URL: {url}[/]")
|
|
299
|
+
else:
|
|
300
|
+
console.print(f"[{SUCCESS_STYLE}]ā
GL AIP ready[/] (SDK v{sdk_version()})")
|
|
301
|
+
ready_printed = True
|
|
302
|
+
elif source == "flag":
|
|
303
|
+
console.print(f"[{SUCCESS_STYLE}]Account: (source={source})[/]")
|
|
304
|
+
else:
|
|
305
|
+
console.print(f"[{SUCCESS_STYLE}]ā
GL AIP ready[/] (SDK v{sdk_version()})")
|
|
306
|
+
ready_printed = True
|
|
307
|
+
|
|
308
|
+
return ready_printed
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _collect_cache_summary() -> tuple[str | None, str | None]:
|
|
312
|
+
"""Collect transcript cache summary and optional note."""
|
|
313
|
+
try:
|
|
314
|
+
cache_stats = get_transcript_cache_stats()
|
|
315
|
+
except Exception:
|
|
316
|
+
return "[dim]Saved transcripts[/dim]: unavailable", None
|
|
317
|
+
|
|
318
|
+
runs_text = f"{cache_stats.entry_count} runs saved"
|
|
319
|
+
if cache_stats.total_bytes:
|
|
320
|
+
size_part = f" Ā· {format_size(cache_stats.total_bytes)} used"
|
|
321
|
+
else:
|
|
322
|
+
size_part = ""
|
|
323
|
+
|
|
324
|
+
cache_line = f"[dim]Saved transcripts[/dim]: {runs_text}{size_part} Ā· {cache_stats.cache_dir}"
|
|
325
|
+
return cache_line, None
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _display_cache_summary(console: Console, slash_mode: bool, cache_line: str | None, cache_note: str | None) -> None:
|
|
329
|
+
"""Render the cache summary details."""
|
|
330
|
+
if cache_line:
|
|
331
|
+
console.print(cache_line)
|
|
332
|
+
if cache_note and not slash_mode:
|
|
333
|
+
console.print(cache_note)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _safe_list_call(obj: Any, attr: str) -> list[Any]:
|
|
337
|
+
"""Call list-like client methods defensively, returning an empty list on failure."""
|
|
338
|
+
func = getattr(obj, attr, None)
|
|
339
|
+
if callable(func):
|
|
340
|
+
try:
|
|
341
|
+
return func()
|
|
342
|
+
except Exception as exc:
|
|
343
|
+
logging.getLogger(__name__).debug(
|
|
344
|
+
"Failed to call %s on %s: %s", attr, type(obj).__name__, exc, exc_info=True
|
|
345
|
+
)
|
|
346
|
+
return []
|
|
347
|
+
return []
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _get_client_from_config(config: dict) -> Any:
|
|
351
|
+
"""Return a Client instance built from config."""
|
|
352
|
+
client_class = _resolve_client_class()
|
|
353
|
+
return client_class(
|
|
354
|
+
api_url=config["api_url"],
|
|
355
|
+
api_key=config["api_key"],
|
|
356
|
+
timeout=config.get("timeout", 30.0),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _create_and_test_client(config: dict, console: Console, *, compact: bool = False) -> Any:
|
|
361
|
+
"""Create client and test connection by fetching resources."""
|
|
362
|
+
client: Any = _get_client_from_config(config)
|
|
363
|
+
|
|
364
|
+
# Test connection by listing resources with a spinner where available
|
|
365
|
+
try:
|
|
366
|
+
with spinner_context(
|
|
367
|
+
None,
|
|
368
|
+
"[bold blue]Checking GL AIP statusā¦[/bold blue]",
|
|
369
|
+
console_override=console,
|
|
370
|
+
spinner_style=INFO,
|
|
371
|
+
) as status_indicator:
|
|
372
|
+
update_spinner(status_indicator, "[bold blue]Fetching agentsā¦[/bold blue]")
|
|
373
|
+
agents = client.list_agents()
|
|
374
|
+
|
|
375
|
+
update_spinner(status_indicator, "[bold blue]Fetching toolsā¦[/bold blue]")
|
|
376
|
+
tools = client.list_tools()
|
|
377
|
+
|
|
378
|
+
update_spinner(status_indicator, "[bold blue]Fetching MCPsā¦[/bold blue]")
|
|
379
|
+
mcps = client.list_mcps()
|
|
380
|
+
except Exception as e:
|
|
381
|
+
# Show AIP Ready status even if connection fails
|
|
382
|
+
if compact:
|
|
383
|
+
status_text = "API call failed"
|
|
384
|
+
api_url = getattr(client, "api_url", config.get("api_url", ""))
|
|
385
|
+
console.print(f"[dim]⢠Base URL[/dim]: {api_url} ({status_text})")
|
|
386
|
+
console.print(f"[{ERROR_STYLE}]⢠Error[/]: {e}")
|
|
387
|
+
console.print("[dim]⢠Tip[/dim]: Check network connectivity or API permissions and try again.")
|
|
388
|
+
console.print("[dim]⢠Resources[/dim]: unavailable")
|
|
389
|
+
else:
|
|
390
|
+
api_url = getattr(client, "api_url", config.get("api_url", ""))
|
|
391
|
+
console.print(
|
|
392
|
+
AIPPanel(
|
|
393
|
+
f"[{WARNING_STYLE}]ā ļø Connection established but API call failed[/]\n"
|
|
394
|
+
f"š API URL: {api_url}\n"
|
|
395
|
+
f"ā Error: {e}\n\n"
|
|
396
|
+
f"š” This usually means:\n"
|
|
397
|
+
f" ⢠Network connectivity issues\n"
|
|
398
|
+
f" ⢠API permissions problems\n"
|
|
399
|
+
f" ⢠Backend service issues",
|
|
400
|
+
title="ā ļø Partial Connection",
|
|
401
|
+
border_style=WARNING,
|
|
402
|
+
),
|
|
403
|
+
)
|
|
404
|
+
return client
|
|
405
|
+
|
|
406
|
+
# Create status table
|
|
407
|
+
table = AIPTable(title="š GL AIP Status")
|
|
408
|
+
table.add_column("Resource", style=INFO, width=15)
|
|
409
|
+
table.add_column("Count", style=NEUTRAL, width=10)
|
|
410
|
+
table.add_column("Status", style=SUCCESS_STYLE, width=15)
|
|
411
|
+
|
|
412
|
+
table.add_row("Agents", str(len(agents)), AVAILABLE_STATUS)
|
|
413
|
+
table.add_row("Tools", str(len(tools)), AVAILABLE_STATUS)
|
|
414
|
+
table.add_row("MCPs", str(len(mcps)), AVAILABLE_STATUS)
|
|
415
|
+
|
|
416
|
+
if compact:
|
|
417
|
+
connection_summary = "GL AIP reachable"
|
|
418
|
+
console.print(f"[dim]⢠Base URL[/dim]: {client.api_url} ({connection_summary})")
|
|
419
|
+
console.print(f"[dim]⢠Agent timeout[/dim]: {DEFAULT_AGENT_RUN_TIMEOUT}s")
|
|
420
|
+
console.print(f"[dim]⢠Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}")
|
|
421
|
+
else:
|
|
422
|
+
console.print( # pragma: no cover - UI display formatting
|
|
423
|
+
AIPPanel(
|
|
424
|
+
f"[{SUCCESS_STYLE}]ā
Connected to GL AIP[/]\n"
|
|
425
|
+
f"š API URL: {client.api_url}\n"
|
|
426
|
+
f"{ICON_AGENT} Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
|
|
427
|
+
title="š Connection Status",
|
|
428
|
+
border_style=SUCCESS,
|
|
429
|
+
),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
console.print(table) # pragma: no cover - UI display formatting
|
|
433
|
+
|
|
434
|
+
return client
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _handle_connection_error(config: dict, console: Console, error: Exception) -> None:
|
|
438
|
+
"""Handle connection errors and show troubleshooting information."""
|
|
439
|
+
console.print(
|
|
440
|
+
AIPPanel(
|
|
441
|
+
f"[{ERROR_STYLE}]ā Connection failed[/]\n\n"
|
|
442
|
+
f"š Error: {error}\n\n"
|
|
443
|
+
f"š” Troubleshooting steps:\n"
|
|
444
|
+
f" ⢠Verify your API URL and key are correct\n"
|
|
445
|
+
f" ⢠Check network connectivity to {config.get('api_url', 'your API')}\n"
|
|
446
|
+
f" ⢠Run 'aip configure' to update credentials\n"
|
|
447
|
+
f" ⢠Run 'aip config list' to check configuration",
|
|
448
|
+
title="ā Connection Error",
|
|
449
|
+
border_style=ERROR,
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
# Log and return; callers decide whether to exit.
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@main.command()
|
|
456
|
+
@click.option(
|
|
457
|
+
"--account",
|
|
458
|
+
"account_name",
|
|
459
|
+
help="Target a named account profile for this command",
|
|
460
|
+
)
|
|
461
|
+
@click.pass_context
|
|
462
|
+
def status(ctx: Any, account_name: str | None) -> None:
|
|
463
|
+
"""Show connection status and basic info."""
|
|
464
|
+
config: dict = {}
|
|
465
|
+
console: Console | None = None
|
|
466
|
+
try:
|
|
467
|
+
if account_name:
|
|
468
|
+
if ctx.obj is None:
|
|
469
|
+
ctx.obj = {}
|
|
470
|
+
ctx.obj["account_name"] = account_name
|
|
471
|
+
|
|
472
|
+
console, slash_mode = _resolve_status_console(ctx)
|
|
473
|
+
|
|
474
|
+
# Load and merge configuration
|
|
475
|
+
config = _load_and_merge_config(ctx)
|
|
476
|
+
|
|
477
|
+
ready_printed = _render_status_heading(console, slash_mode, config)
|
|
478
|
+
if not ready_printed:
|
|
479
|
+
console.print(f"[{SUCCESS_STYLE}]ā
GL AIP ready[/] (SDK v{sdk_version()})")
|
|
480
|
+
|
|
481
|
+
cache_result = _collect_cache_summary()
|
|
482
|
+
if isinstance(cache_result, tuple) and len(cache_result) == 2:
|
|
483
|
+
cache_line, cache_note = cache_result
|
|
484
|
+
else:
|
|
485
|
+
cache_line, cache_note = cache_result, None
|
|
486
|
+
_display_cache_summary(console, slash_mode, cache_line, cache_note)
|
|
487
|
+
|
|
488
|
+
# Validate configuration
|
|
489
|
+
_validate_config_and_show_error(config, console)
|
|
490
|
+
|
|
491
|
+
# Create and test client connection using unified compact layout
|
|
492
|
+
client = _create_and_test_client(config, console, compact=True)
|
|
493
|
+
close = getattr(client, "close", None)
|
|
494
|
+
if callable(close):
|
|
495
|
+
try:
|
|
496
|
+
close()
|
|
497
|
+
except Exception:
|
|
498
|
+
pass
|
|
499
|
+
|
|
500
|
+
except Exception as e:
|
|
501
|
+
# Handle any unexpected errors during the process and exit with error code
|
|
502
|
+
fallback_console = console or Console()
|
|
503
|
+
_handle_connection_error(config or {}, fallback_console, e)
|
|
504
|
+
sys.exit(1)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@main.command()
|
|
508
|
+
def version() -> None:
|
|
509
|
+
"""Show version information."""
|
|
510
|
+
branding = AIPBranding.create_from_sdk(sdk_version=sdk_version(), package_name="glaip-sdk")
|
|
511
|
+
branding.display_version_panel()
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
@main.command()
|
|
515
|
+
@click.option("--check-only", is_flag=True, help="Only check for updates without installing")
|
|
516
|
+
@click.option(
|
|
517
|
+
"--force",
|
|
518
|
+
is_flag=True,
|
|
519
|
+
help="Force reinstall even if already up-to-date (adds --force-reinstall)",
|
|
520
|
+
)
|
|
521
|
+
def update(check_only: bool, force: bool) -> None:
|
|
522
|
+
"""Update AIP SDK to the latest version from PyPI."""
|
|
523
|
+
slash_mode = in_slash_mode()
|
|
524
|
+
try:
|
|
525
|
+
console = Console()
|
|
526
|
+
|
|
527
|
+
if check_only:
|
|
528
|
+
console.print(
|
|
529
|
+
AIPPanel(
|
|
530
|
+
"[bold blue]š Checking for updates...[/bold blue]\n\nš” To install updates, run: aip update",
|
|
531
|
+
title="š Update Check",
|
|
532
|
+
border_style="blue",
|
|
533
|
+
),
|
|
534
|
+
)
|
|
535
|
+
return
|
|
536
|
+
|
|
537
|
+
update_hint = ""
|
|
538
|
+
if not slash_mode:
|
|
539
|
+
update_hint = "\nš” Use --check-only to just check for updates"
|
|
540
|
+
|
|
541
|
+
console.print(
|
|
542
|
+
AIPPanel(
|
|
543
|
+
"[bold blue]š Updating AIP SDK...[/bold blue]\n\n"
|
|
544
|
+
"š¦ This will update the package from PyPI"
|
|
545
|
+
f"{update_hint}",
|
|
546
|
+
title="Update Process",
|
|
547
|
+
border_style="blue",
|
|
548
|
+
padding=(0, 1),
|
|
549
|
+
),
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Update using pip
|
|
553
|
+
try:
|
|
554
|
+
cmd = list(_build_upgrade_command(include_prerelease=False))
|
|
555
|
+
# Replace package name with "glaip-sdk" (main.py uses different name)
|
|
556
|
+
cmd[-1] = "glaip-sdk"
|
|
557
|
+
if force:
|
|
558
|
+
cmd.insert(5, "--force-reinstall")
|
|
559
|
+
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
560
|
+
|
|
561
|
+
verify_hint = ""
|
|
562
|
+
if not slash_mode:
|
|
563
|
+
verify_hint = "\nš” Restart your terminal or run 'aip --version' to verify"
|
|
564
|
+
|
|
565
|
+
console.print(
|
|
566
|
+
AIPPanel(
|
|
567
|
+
f"[{SUCCESS_STYLE}]ā
Update successful![/]\n\n"
|
|
568
|
+
"š AIP SDK has been updated to the latest version"
|
|
569
|
+
f"{verify_hint}",
|
|
570
|
+
title="š Update Complete",
|
|
571
|
+
border_style=SUCCESS,
|
|
572
|
+
padding=(0, 1),
|
|
573
|
+
),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Show new version
|
|
577
|
+
version_result = subprocess.run(
|
|
578
|
+
[sys.executable, "-m", "glaip_sdk.cli.main", "--version"],
|
|
579
|
+
capture_output=True,
|
|
580
|
+
text=True,
|
|
581
|
+
check=True,
|
|
582
|
+
)
|
|
583
|
+
console.print(f"š New version: {version_result.stdout.strip()}")
|
|
584
|
+
|
|
585
|
+
except subprocess.CalledProcessError as e:
|
|
586
|
+
console.print(
|
|
587
|
+
AIPPanel(
|
|
588
|
+
f"[{ERROR_STYLE}]ā Update failed[/]\n\n"
|
|
589
|
+
f"š Error: {e.stderr}\n\n"
|
|
590
|
+
"š” Troubleshooting:\n"
|
|
591
|
+
" ⢠Check your internet connection\n"
|
|
592
|
+
" ⢠Try running: pip install --upgrade glaip-sdk\n"
|
|
593
|
+
" ⢠Check if you have write permissions",
|
|
594
|
+
title="ā Update Error",
|
|
595
|
+
border_style=ERROR,
|
|
596
|
+
padding=(0, 1),
|
|
597
|
+
),
|
|
598
|
+
)
|
|
599
|
+
sys.exit(1)
|
|
600
|
+
|
|
601
|
+
except ImportError:
|
|
602
|
+
console.print(
|
|
603
|
+
AIPPanel(
|
|
604
|
+
f"[{ERROR_STYLE}]ā Rich library not available[/]\n\n"
|
|
605
|
+
"š” Install rich: pip install rich\n"
|
|
606
|
+
" Then try: aip update",
|
|
607
|
+
title="ā Missing Dependency",
|
|
608
|
+
border_style=ERROR,
|
|
609
|
+
),
|
|
610
|
+
)
|
|
611
|
+
sys.exit(1)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
if __name__ == "__main__":
|
|
615
|
+
main() # pylint: disable=no-value-for-parameter
|