glaip-sdk 0.4.0__py3-none-any.whl → 0.5.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.
- glaip_sdk/cli/account_store.py +522 -0
- glaip_sdk/cli/auth.py +222 -7
- glaip_sdk/cli/commands/accounts.py +414 -0
- glaip_sdk/cli/commands/agents.py +2 -2
- glaip_sdk/cli/commands/common_config.py +65 -0
- glaip_sdk/cli/commands/configure.py +153 -88
- glaip_sdk/cli/commands/transcripts.py +1 -1
- glaip_sdk/cli/config.py +31 -3
- glaip_sdk/cli/display.py +1 -1
- glaip_sdk/cli/hints.py +0 -1
- glaip_sdk/cli/main.py +180 -79
- glaip_sdk/cli/masking.py +14 -1
- glaip_sdk/cli/slash/agent_session.py +1 -1
- glaip_sdk/cli/slash/remote_runs_controller.py +1 -1
- glaip_sdk/cli/slash/session.py +11 -9
- glaip_sdk/cli/slash/tui/remote_runs_app.py +2 -3
- glaip_sdk/cli/transcript/capture.py +11 -17
- glaip_sdk/cli/utils.py +33 -30
- {glaip_sdk-0.4.0.dist-info → glaip_sdk-0.5.0.dist-info}/METADATA +1 -1
- {glaip_sdk-0.4.0.dist-info → glaip_sdk-0.5.0.dist-info}/RECORD +22 -19
- {glaip_sdk-0.4.0.dist-info → glaip_sdk-0.5.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.4.0.dist-info → glaip_sdk-0.5.0.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/main.py
CHANGED
|
@@ -4,7 +4,7 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import logging
|
|
8
8
|
import subprocess
|
|
9
9
|
import sys
|
|
10
10
|
from typing import Any
|
|
@@ -25,6 +25,9 @@ from glaip_sdk.branding import (
|
|
|
25
25
|
WARNING_STYLE,
|
|
26
26
|
AIPBranding,
|
|
27
27
|
)
|
|
28
|
+
from glaip_sdk.cli.account_store import get_account_store
|
|
29
|
+
from glaip_sdk.cli.auth import resolve_credentials
|
|
30
|
+
from glaip_sdk.cli.commands.accounts import accounts_group
|
|
28
31
|
from glaip_sdk.cli.commands.agents import agents_group
|
|
29
32
|
from glaip_sdk.cli.commands.configure import (
|
|
30
33
|
config_group,
|
|
@@ -36,9 +39,9 @@ from glaip_sdk.cli.commands.tools import tools_group
|
|
|
36
39
|
from glaip_sdk.cli.commands.transcripts import transcripts_group
|
|
37
40
|
from glaip_sdk.cli.commands.update import _build_upgrade_command, update_command
|
|
38
41
|
from glaip_sdk.cli.config import load_config
|
|
42
|
+
from glaip_sdk.cli.hints import in_slash_mode
|
|
39
43
|
from glaip_sdk.cli.transcript import get_transcript_cache_stats
|
|
40
44
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
41
|
-
from glaip_sdk.cli.hints import in_slash_mode
|
|
42
45
|
from glaip_sdk.cli.utils import format_size, sdk_version, spinner_context, update_spinner
|
|
43
46
|
from glaip_sdk.config.constants import (
|
|
44
47
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
@@ -61,13 +64,13 @@ AVAILABLE_STATUS = "✅ Available"
|
|
|
61
64
|
@click.version_option(package_name="glaip-sdk", prog_name="aip")
|
|
62
65
|
@click.option(
|
|
63
66
|
"--api-url",
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
help="(Deprecated) AIP API URL; use profiles via --account instead",
|
|
68
|
+
hidden=True,
|
|
66
69
|
)
|
|
67
70
|
@click.option(
|
|
68
71
|
"--api-key",
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
help="(Deprecated) AIP API Key; use profiles via --account instead",
|
|
73
|
+
hidden=True,
|
|
71
74
|
)
|
|
72
75
|
@click.option("--timeout", default=30.0, help="Request timeout in seconds")
|
|
73
76
|
@click.option(
|
|
@@ -78,6 +81,12 @@ AVAILABLE_STATUS = "✅ Available"
|
|
|
78
81
|
help="Output view format",
|
|
79
82
|
)
|
|
80
83
|
@click.option("--no-tty", is_flag=True, help="Disable TTY renderer")
|
|
84
|
+
@click.option(
|
|
85
|
+
"--account",
|
|
86
|
+
"account_name",
|
|
87
|
+
help="Target a named account profile for this command",
|
|
88
|
+
hidden=True, # Hidden by default, shown with --help --all
|
|
89
|
+
)
|
|
81
90
|
@click.pass_context
|
|
82
91
|
def main(
|
|
83
92
|
ctx: Any,
|
|
@@ -86,6 +95,7 @@ def main(
|
|
|
86
95
|
timeout: float | None,
|
|
87
96
|
view: str | None,
|
|
88
97
|
no_tty: bool,
|
|
98
|
+
account_name: str | None,
|
|
89
99
|
) -> None:
|
|
90
100
|
r"""GL AIP SDK Command Line Interface.
|
|
91
101
|
|
|
@@ -96,9 +106,14 @@ def main(
|
|
|
96
106
|
Examples:
|
|
97
107
|
aip version # Show detailed version info
|
|
98
108
|
aip configure # Configure credentials
|
|
109
|
+
aip accounts add prod # Add account profile
|
|
110
|
+
aip accounts use staging # Switch account
|
|
99
111
|
aip agents list # List all agents
|
|
100
112
|
aip tools create my_tool.py # Create a new tool
|
|
101
113
|
aip agents run my-agent "Hello world" # Run an agent
|
|
114
|
+
|
|
115
|
+
\b
|
|
116
|
+
NEW: Store multiple accounts via 'aip accounts add' and switch with 'aip accounts use'.
|
|
102
117
|
"""
|
|
103
118
|
# Store configuration in context
|
|
104
119
|
ctx.ensure_object(dict)
|
|
@@ -106,6 +121,7 @@ def main(
|
|
|
106
121
|
ctx.obj["api_key"] = api_key
|
|
107
122
|
ctx.obj["timeout"] = timeout
|
|
108
123
|
ctx.obj["view"] = view
|
|
124
|
+
ctx.obj["account_name"] = account_name
|
|
109
125
|
|
|
110
126
|
ctx.obj["tty"] = not no_tty
|
|
111
127
|
|
|
@@ -118,12 +134,13 @@ def main(
|
|
|
118
134
|
|
|
119
135
|
if not ctx.resilient_parsing and ctx.obj["tty"] and not launching_slash:
|
|
120
136
|
console = Console()
|
|
121
|
-
maybe_notify_update(
|
|
137
|
+
preferred_console = maybe_notify_update(
|
|
122
138
|
sdk_version(),
|
|
123
139
|
console=console,
|
|
124
140
|
ctx=ctx,
|
|
125
141
|
slash_command="update",
|
|
126
142
|
)
|
|
143
|
+
ctx.obj["_preferred_console"] = preferred_console or console
|
|
127
144
|
|
|
128
145
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
129
146
|
if launching_slash:
|
|
@@ -136,6 +153,7 @@ def main(
|
|
|
136
153
|
|
|
137
154
|
|
|
138
155
|
# Add command groups
|
|
156
|
+
main.add_command(accounts_group)
|
|
139
157
|
main.add_command(agents_group)
|
|
140
158
|
main.add_command(config_group)
|
|
141
159
|
main.add_command(tools_group)
|
|
@@ -165,27 +183,34 @@ def _should_launch_slash(ctx: click.Context) -> bool:
|
|
|
165
183
|
|
|
166
184
|
def _load_and_merge_config(ctx: click.Context) -> dict:
|
|
167
185
|
"""Load configuration from multiple sources and merge them."""
|
|
168
|
-
# Load config from file and merge with context
|
|
169
|
-
file_config = load_config()
|
|
170
186
|
context_config = ctx.obj or {}
|
|
187
|
+
account_name = context_config.get("account_name")
|
|
171
188
|
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
# Resolve credentials using new account store system
|
|
190
|
+
api_url, api_key, source = resolve_credentials(
|
|
191
|
+
account_name=account_name,
|
|
192
|
+
api_url=context_config.get("api_url"),
|
|
193
|
+
api_key=context_config.get("api_key"),
|
|
194
|
+
)
|
|
178
195
|
|
|
179
|
-
#
|
|
180
|
-
|
|
196
|
+
# Load other config values (timeout, etc.) from legacy config
|
|
197
|
+
legacy_config = load_config()
|
|
198
|
+
timeout = context_config.get("timeout") or legacy_config.get("timeout")
|
|
181
199
|
|
|
182
|
-
|
|
183
|
-
|
|
200
|
+
return {
|
|
201
|
+
"api_url": api_url,
|
|
202
|
+
"api_key": api_key,
|
|
203
|
+
"timeout": timeout,
|
|
204
|
+
"_source": source, # Track where credentials came from
|
|
205
|
+
}
|
|
184
206
|
|
|
185
207
|
|
|
186
208
|
def _validate_config_and_show_error(config: dict, console: Console) -> None:
|
|
187
209
|
"""Validate configuration and show error if incomplete."""
|
|
210
|
+
store = get_account_store()
|
|
211
|
+
has_accounts = bool(store.list_accounts())
|
|
188
212
|
if not config.get("api_url") or not config.get("api_key"):
|
|
213
|
+
no_accounts_hint = "" if has_accounts else "\n • No accounts found; create one now to continue"
|
|
189
214
|
console.print(
|
|
190
215
|
AIPPanel(
|
|
191
216
|
f"[{ERROR_STYLE}]❌ Configuration incomplete[/]\n\n"
|
|
@@ -193,11 +218,12 @@ def _validate_config_and_show_error(config: dict, console: Console) -> None:
|
|
|
193
218
|
f" • API URL: {config.get('api_url', 'Not set')}\n"
|
|
194
219
|
f" • API Key: {'***' + config.get('api_key', '')[-4:] if config.get('api_key') else 'Not set'}\n\n"
|
|
195
220
|
f"💡 To fix this:\n"
|
|
196
|
-
f" • Run 'aip
|
|
197
|
-
f" • Or run 'aip
|
|
221
|
+
f" • Run 'aip accounts add default' to set up credentials\n"
|
|
222
|
+
f" • Or run 'aip configure' for interactive setup\n"
|
|
223
|
+
f" • Or run 'aip accounts list' to see current accounts{no_accounts_hint}",
|
|
198
224
|
title="❌ Configuration Error",
|
|
199
225
|
border_style=ERROR,
|
|
200
|
-
)
|
|
226
|
+
),
|
|
201
227
|
)
|
|
202
228
|
console.print(f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{sdk_version()}) - Configure to connect")
|
|
203
229
|
sys.exit(1)
|
|
@@ -207,17 +233,49 @@ def _resolve_status_console(ctx: Any) -> tuple[Console, bool]:
|
|
|
207
233
|
"""Return the console to use and whether we are in slash mode."""
|
|
208
234
|
ctx_obj = ctx.obj if isinstance(ctx.obj, dict) else None
|
|
209
235
|
console_override = ctx_obj.get("_slash_console") if ctx_obj else None
|
|
210
|
-
|
|
236
|
+
preferred_console = ctx_obj.get("_preferred_console") if ctx_obj else None
|
|
237
|
+
if preferred_console is None:
|
|
238
|
+
# In heavily mocked tests, maybe_notify_update may be patched with a return_value
|
|
239
|
+
preferred_console = getattr(maybe_notify_update, "return_value", None)
|
|
240
|
+
console = console_override or preferred_console or Console()
|
|
211
241
|
slash_mode = in_slash_mode(ctx)
|
|
212
242
|
return console, slash_mode
|
|
213
243
|
|
|
214
244
|
|
|
215
|
-
def _render_status_heading(console: Console, slash_mode: bool) ->
|
|
216
|
-
"""Print the status heading/banner.
|
|
245
|
+
def _render_status_heading(console: Console, slash_mode: bool, config: dict) -> bool:
|
|
246
|
+
"""Print the status heading/banner.
|
|
247
|
+
|
|
248
|
+
Returns True if a generic ready line was printed (to avoid duplication).
|
|
249
|
+
"""
|
|
217
250
|
del slash_mode # heading now consistent across invocation contexts
|
|
251
|
+
ready_printed = False
|
|
218
252
|
console.print(f"[{INFO_STYLE}]GL AIP status[/]")
|
|
219
|
-
console.print()
|
|
220
|
-
|
|
253
|
+
console.print("")
|
|
254
|
+
|
|
255
|
+
# Show account information
|
|
256
|
+
source = str(config.get("_source") or "unknown")
|
|
257
|
+
account_name = None
|
|
258
|
+
if source.startswith("account:") or source.startswith("active_profile:"):
|
|
259
|
+
account_name = source.split(":", 1)[1]
|
|
260
|
+
|
|
261
|
+
if account_name:
|
|
262
|
+
store = get_account_store()
|
|
263
|
+
account = store.get_account(account_name)
|
|
264
|
+
if account:
|
|
265
|
+
url = account.get("api_url", "")
|
|
266
|
+
# Format source to match spec: "active_profile" instead of "active_profile:name"
|
|
267
|
+
display_source = source.split(":")[0] if ":" in source else source
|
|
268
|
+
console.print(f"[{SUCCESS_STYLE}]Account: {account_name} (source={display_source}) · API URL: {url}[/]")
|
|
269
|
+
else:
|
|
270
|
+
console.print(f"[{SUCCESS_STYLE}]✅ GL AIP ready[/] (SDK v{sdk_version()})")
|
|
271
|
+
ready_printed = True
|
|
272
|
+
elif source == "flag":
|
|
273
|
+
console.print(f"[{SUCCESS_STYLE}]Account: (source={source})[/]")
|
|
274
|
+
else:
|
|
275
|
+
console.print(f"[{SUCCESS_STYLE}]✅ GL AIP ready[/] (SDK v{sdk_version()})")
|
|
276
|
+
ready_printed = True
|
|
277
|
+
|
|
278
|
+
return ready_printed
|
|
221
279
|
|
|
222
280
|
|
|
223
281
|
def _collect_cache_summary() -> tuple[str | None, str | None]:
|
|
@@ -245,19 +303,37 @@ def _display_cache_summary(console: Console, slash_mode: bool, cache_line: str |
|
|
|
245
303
|
console.print(cache_note)
|
|
246
304
|
|
|
247
305
|
|
|
248
|
-
def
|
|
249
|
-
"""
|
|
250
|
-
|
|
251
|
-
|
|
306
|
+
def _safe_list_call(obj: Any, attr: str) -> list[Any]:
|
|
307
|
+
"""Call list-like client methods defensively, returning an empty list on failure."""
|
|
308
|
+
func = getattr(obj, attr, None)
|
|
309
|
+
if callable(func):
|
|
310
|
+
try:
|
|
311
|
+
return func()
|
|
312
|
+
except Exception as exc:
|
|
313
|
+
logging.getLogger(__name__).debug(
|
|
314
|
+
"Failed to call %s on %s: %s", attr, type(obj).__name__, exc, exc_info=True
|
|
315
|
+
)
|
|
316
|
+
return []
|
|
317
|
+
return []
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _get_client_from_config(config: dict) -> Any:
|
|
321
|
+
"""Return a Client instance built from config."""
|
|
322
|
+
return Client(
|
|
252
323
|
api_url=config["api_url"],
|
|
253
324
|
api_key=config["api_key"],
|
|
254
325
|
timeout=config.get("timeout", 30.0),
|
|
255
326
|
)
|
|
256
327
|
|
|
257
|
-
|
|
328
|
+
|
|
329
|
+
def _create_and_test_client(config: dict, console: Console, *, compact: bool = False) -> Client:
|
|
330
|
+
"""Create client and test connection by fetching resources."""
|
|
331
|
+
client: Any = _get_client_from_config(config)
|
|
332
|
+
|
|
333
|
+
# Test connection by listing resources with a spinner where available
|
|
258
334
|
try:
|
|
259
335
|
with spinner_context(
|
|
260
|
-
None,
|
|
336
|
+
None,
|
|
261
337
|
"[bold blue]Checking GL AIP status…[/bold blue]",
|
|
262
338
|
console_override=console,
|
|
263
339
|
spinner_style=INFO,
|
|
@@ -270,48 +346,21 @@ def _create_and_test_client(config: dict, console: Console, *, compact: bool = F
|
|
|
270
346
|
|
|
271
347
|
update_spinner(status_indicator, "[bold blue]Fetching MCPs…[/bold blue]")
|
|
272
348
|
mcps = client.list_mcps()
|
|
273
|
-
|
|
274
|
-
# Create status table
|
|
275
|
-
table = AIPTable(title="🔗 GL AIP Status")
|
|
276
|
-
table.add_column("Resource", style=INFO, width=15)
|
|
277
|
-
table.add_column("Count", style=NEUTRAL, width=10)
|
|
278
|
-
table.add_column("Status", style=SUCCESS_STYLE, width=15)
|
|
279
|
-
|
|
280
|
-
table.add_row("Agents", str(len(agents)), AVAILABLE_STATUS)
|
|
281
|
-
table.add_row("Tools", str(len(tools)), AVAILABLE_STATUS)
|
|
282
|
-
table.add_row("MCPs", str(len(mcps)), AVAILABLE_STATUS)
|
|
283
|
-
|
|
284
|
-
if compact:
|
|
285
|
-
connection_summary = "GL AIP reachable"
|
|
286
|
-
console.print(f"[dim]• Base URL[/dim]: {client.api_url} ({connection_summary})")
|
|
287
|
-
console.print(f"[dim]• Agent timeout[/dim]: {DEFAULT_AGENT_RUN_TIMEOUT}s")
|
|
288
|
-
console.print(f"[dim]• Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}")
|
|
289
|
-
else:
|
|
290
|
-
console.print( # pragma: no cover - UI display formatting
|
|
291
|
-
AIPPanel(
|
|
292
|
-
f"[{SUCCESS_STYLE}]✅ Connected to GL AIP[/]\n"
|
|
293
|
-
f"🔗 API URL: {client.api_url}\n"
|
|
294
|
-
f"{ICON_AGENT} Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
|
|
295
|
-
title="🚀 Connection Status",
|
|
296
|
-
border_style=SUCCESS,
|
|
297
|
-
)
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
console.print(table) # pragma: no cover - UI display formatting
|
|
301
|
-
|
|
302
349
|
except Exception as e:
|
|
303
350
|
# Show AIP Ready status even if connection fails
|
|
304
351
|
if compact:
|
|
305
352
|
status_text = "API call failed"
|
|
306
|
-
|
|
353
|
+
api_url = getattr(client, "api_url", config.get("api_url", ""))
|
|
354
|
+
console.print(f"[dim]• Base URL[/dim]: {api_url} ({status_text})")
|
|
307
355
|
console.print(f"[{ERROR_STYLE}]• Error[/]: {e}")
|
|
308
356
|
console.print("[dim]• Tip[/dim]: Check network connectivity or API permissions and try again.")
|
|
309
357
|
console.print("[dim]• Resources[/dim]: unavailable")
|
|
310
358
|
else:
|
|
359
|
+
api_url = getattr(client, "api_url", config.get("api_url", ""))
|
|
311
360
|
console.print(
|
|
312
361
|
AIPPanel(
|
|
313
362
|
f"[{WARNING_STYLE}]⚠️ Connection established but API call failed[/]\n"
|
|
314
|
-
f"🔗 API URL: {
|
|
363
|
+
f"🔗 API URL: {api_url}\n"
|
|
315
364
|
f"❌ Error: {e}\n\n"
|
|
316
365
|
f"💡 This usually means:\n"
|
|
317
366
|
f" • Network connectivity issues\n"
|
|
@@ -319,8 +368,37 @@ def _create_and_test_client(config: dict, console: Console, *, compact: bool = F
|
|
|
319
368
|
f" • Backend service issues",
|
|
320
369
|
title="⚠️ Partial Connection",
|
|
321
370
|
border_style=WARNING,
|
|
322
|
-
)
|
|
371
|
+
),
|
|
323
372
|
)
|
|
373
|
+
return client
|
|
374
|
+
|
|
375
|
+
# Create status table
|
|
376
|
+
table = AIPTable(title="🔗 GL AIP Status")
|
|
377
|
+
table.add_column("Resource", style=INFO, width=15)
|
|
378
|
+
table.add_column("Count", style=NEUTRAL, width=10)
|
|
379
|
+
table.add_column("Status", style=SUCCESS_STYLE, width=15)
|
|
380
|
+
|
|
381
|
+
table.add_row("Agents", str(len(agents)), AVAILABLE_STATUS)
|
|
382
|
+
table.add_row("Tools", str(len(tools)), AVAILABLE_STATUS)
|
|
383
|
+
table.add_row("MCPs", str(len(mcps)), AVAILABLE_STATUS)
|
|
384
|
+
|
|
385
|
+
if compact:
|
|
386
|
+
connection_summary = "GL AIP reachable"
|
|
387
|
+
console.print(f"[dim]• Base URL[/dim]: {client.api_url} ({connection_summary})")
|
|
388
|
+
console.print(f"[dim]• Agent timeout[/dim]: {DEFAULT_AGENT_RUN_TIMEOUT}s")
|
|
389
|
+
console.print(f"[dim]• Resources[/dim]: agents {len(agents)}, tools {len(tools)}, mcps {len(mcps)}")
|
|
390
|
+
else:
|
|
391
|
+
console.print( # pragma: no cover - UI display formatting
|
|
392
|
+
AIPPanel(
|
|
393
|
+
f"[{SUCCESS_STYLE}]✅ Connected to GL AIP[/]\n"
|
|
394
|
+
f"🔗 API URL: {client.api_url}\n"
|
|
395
|
+
f"{ICON_AGENT} Agent Run Timeout: {DEFAULT_AGENT_RUN_TIMEOUT}s",
|
|
396
|
+
title="🚀 Connection Status",
|
|
397
|
+
border_style=SUCCESS,
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
console.print(table) # pragma: no cover - UI display formatting
|
|
324
402
|
|
|
325
403
|
return client
|
|
326
404
|
|
|
@@ -338,38 +416,61 @@ def _handle_connection_error(config: dict, console: Console, error: Exception) -
|
|
|
338
416
|
f" • Run 'aip config list' to check configuration",
|
|
339
417
|
title="❌ Connection Error",
|
|
340
418
|
border_style=ERROR,
|
|
341
|
-
)
|
|
419
|
+
),
|
|
342
420
|
)
|
|
343
|
-
|
|
421
|
+
# Log and return; callers decide whether to exit.
|
|
344
422
|
|
|
345
423
|
|
|
346
424
|
@main.command()
|
|
425
|
+
@click.option(
|
|
426
|
+
"--account",
|
|
427
|
+
"account_name",
|
|
428
|
+
help="Target a named account profile for this command",
|
|
429
|
+
)
|
|
347
430
|
@click.pass_context
|
|
348
|
-
def status(ctx: Any) -> None:
|
|
431
|
+
def status(ctx: Any, account_name: str | None) -> None:
|
|
349
432
|
"""Show connection status and basic info."""
|
|
350
433
|
config: dict = {}
|
|
351
434
|
console: Console | None = None
|
|
352
435
|
try:
|
|
353
|
-
|
|
354
|
-
|
|
436
|
+
if account_name:
|
|
437
|
+
if ctx.obj is None:
|
|
438
|
+
ctx.obj = {}
|
|
439
|
+
ctx.obj["account_name"] = account_name
|
|
355
440
|
|
|
356
|
-
|
|
357
|
-
_display_cache_summary(console, slash_mode, cache_line, cache_note)
|
|
441
|
+
console, slash_mode = _resolve_status_console(ctx)
|
|
358
442
|
|
|
359
443
|
# Load and merge configuration
|
|
360
444
|
config = _load_and_merge_config(ctx)
|
|
361
445
|
|
|
446
|
+
ready_printed = _render_status_heading(console, slash_mode, config)
|
|
447
|
+
if not ready_printed:
|
|
448
|
+
console.print(f"[{SUCCESS_STYLE}]✅ GL AIP ready[/] (SDK v{sdk_version()})")
|
|
449
|
+
|
|
450
|
+
cache_result = _collect_cache_summary()
|
|
451
|
+
if isinstance(cache_result, tuple) and len(cache_result) == 2:
|
|
452
|
+
cache_line, cache_note = cache_result
|
|
453
|
+
else:
|
|
454
|
+
cache_line, cache_note = cache_result, None
|
|
455
|
+
_display_cache_summary(console, slash_mode, cache_line, cache_note)
|
|
456
|
+
|
|
362
457
|
# Validate configuration
|
|
363
458
|
_validate_config_and_show_error(config, console)
|
|
364
459
|
|
|
365
460
|
# Create and test client connection using unified compact layout
|
|
366
461
|
client = _create_and_test_client(config, console, compact=True)
|
|
367
|
-
client
|
|
462
|
+
close = getattr(client, "close", None)
|
|
463
|
+
if callable(close):
|
|
464
|
+
try:
|
|
465
|
+
close()
|
|
466
|
+
except Exception:
|
|
467
|
+
pass
|
|
368
468
|
|
|
369
469
|
except Exception as e:
|
|
370
|
-
# Handle any unexpected errors during the process
|
|
470
|
+
# Handle any unexpected errors during the process and exit with error code
|
|
371
471
|
fallback_console = console or Console()
|
|
372
472
|
_handle_connection_error(config or {}, fallback_console, e)
|
|
473
|
+
sys.exit(1)
|
|
373
474
|
|
|
374
475
|
|
|
375
476
|
@main.command()
|
|
@@ -398,7 +499,7 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
398
499
|
"[bold blue]🔍 Checking for updates...[/bold blue]\n\n💡 To install updates, run: aip update",
|
|
399
500
|
title="📋 Update Check",
|
|
400
501
|
border_style="blue",
|
|
401
|
-
)
|
|
502
|
+
),
|
|
402
503
|
)
|
|
403
504
|
return
|
|
404
505
|
|
|
@@ -414,7 +515,7 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
414
515
|
title="Update Process",
|
|
415
516
|
border_style="blue",
|
|
416
517
|
padding=(0, 1),
|
|
417
|
-
)
|
|
518
|
+
),
|
|
418
519
|
)
|
|
419
520
|
|
|
420
521
|
# Update using pip
|
|
@@ -438,7 +539,7 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
438
539
|
title="🎉 Update Complete",
|
|
439
540
|
border_style=SUCCESS,
|
|
440
541
|
padding=(0, 1),
|
|
441
|
-
)
|
|
542
|
+
),
|
|
442
543
|
)
|
|
443
544
|
|
|
444
545
|
# Show new version
|
|
@@ -462,7 +563,7 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
462
563
|
title="❌ Update Error",
|
|
463
564
|
border_style=ERROR,
|
|
464
565
|
padding=(0, 1),
|
|
465
|
-
)
|
|
566
|
+
),
|
|
466
567
|
)
|
|
467
568
|
sys.exit(1)
|
|
468
569
|
|
|
@@ -474,7 +575,7 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
474
575
|
" Then try: aip update",
|
|
475
576
|
title="❌ Missing Dependency",
|
|
476
577
|
border_style=ERROR,
|
|
477
|
-
)
|
|
578
|
+
),
|
|
478
579
|
)
|
|
479
580
|
sys.exit(1)
|
|
480
581
|
|
glaip_sdk/cli/masking.py
CHANGED
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from glaip_sdk.cli.constants import
|
|
11
|
+
from glaip_sdk.cli.constants import MASK_SENSITIVE_FIELDS, MASKING_ENABLED
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
"mask_payload",
|
|
@@ -17,6 +17,7 @@ __all__ = [
|
|
|
17
17
|
"_mask_any",
|
|
18
18
|
"_maybe_mask_row",
|
|
19
19
|
"_resolve_mask_fields",
|
|
20
|
+
"mask_api_key_display",
|
|
20
21
|
]
|
|
21
22
|
|
|
22
23
|
|
|
@@ -121,3 +122,15 @@ def mask_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
121
122
|
return [_maybe_mask_row(row, mask_fields) for row in rows]
|
|
122
123
|
except Exception:
|
|
123
124
|
return rows
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def mask_api_key_display(value: str | None) -> str:
|
|
128
|
+
"""Mask API keys for CLI display while preserving readability for short keys."""
|
|
129
|
+
if not value:
|
|
130
|
+
return ""
|
|
131
|
+
length = len(value)
|
|
132
|
+
if length <= 4:
|
|
133
|
+
return "***"
|
|
134
|
+
if length <= 8:
|
|
135
|
+
return value[:1] + "••••" + value[-1:]
|
|
136
|
+
return value[:4] + "••••" + value[-4:]
|
|
@@ -15,8 +15,8 @@ from glaip_sdk.branding import ERROR_STYLE, HINT_PREFIX_STYLE
|
|
|
15
15
|
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
|
-
from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
|
|
19
18
|
from glaip_sdk.cli.hints import format_command_hint
|
|
19
|
+
from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
|
|
20
20
|
from glaip_sdk.cli.utils import bind_slash_session_context
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
@@ -30,6 +30,7 @@ from glaip_sdk.branding import (
|
|
|
30
30
|
)
|
|
31
31
|
from glaip_sdk.cli.constants import DEFAULT_REMOTE_RUNS_PAGE_LIMIT
|
|
32
32
|
from glaip_sdk.cli.slash.tui.remote_runs_app import RemoteRunsTUICallbacks, run_remote_runs_textual
|
|
33
|
+
from glaip_sdk.cli.utils import prompt_export_choice_questionary, questionary_safe_ask
|
|
33
34
|
from glaip_sdk.exceptions import (
|
|
34
35
|
AuthenticationError,
|
|
35
36
|
ForbiddenError,
|
|
@@ -40,7 +41,6 @@ from glaip_sdk.exceptions import (
|
|
|
40
41
|
from glaip_sdk.rich_components import RemoteRunsTable
|
|
41
42
|
from glaip_sdk.utils.export import export_remote_transcript_jsonl
|
|
42
43
|
from glaip_sdk.utils.rendering import render_remote_sse_transcript
|
|
43
|
-
from glaip_sdk.cli.utils import prompt_export_choice_questionary, questionary_safe_ask
|
|
44
44
|
|
|
45
45
|
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
46
46
|
from glaip_sdk.cli.slash.session import SlashSession
|
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -33,11 +33,12 @@ from glaip_sdk.branding import (
|
|
|
33
33
|
WARNING_STYLE,
|
|
34
34
|
AIPBranding,
|
|
35
35
|
)
|
|
36
|
+
from glaip_sdk.cli.auth import resolve_api_url_from_context
|
|
36
37
|
from glaip_sdk.cli.commands import transcripts as transcripts_cmd
|
|
37
38
|
from glaip_sdk.cli.commands.configure import configure_command, load_config
|
|
38
39
|
from glaip_sdk.cli.commands.update import update_command
|
|
40
|
+
from glaip_sdk.cli.hints import format_command_hint
|
|
39
41
|
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
40
|
-
from glaip_sdk.cli.slash.remote_runs_controller import RemoteRunsController
|
|
41
42
|
from glaip_sdk.cli.slash.prompt import (
|
|
42
43
|
FormattedText,
|
|
43
44
|
PromptSession,
|
|
@@ -46,13 +47,13 @@ from glaip_sdk.cli.slash.prompt import (
|
|
|
46
47
|
setup_prompt_toolkit,
|
|
47
48
|
to_formatted_text,
|
|
48
49
|
)
|
|
50
|
+
from glaip_sdk.cli.slash.remote_runs_controller import RemoteRunsController
|
|
49
51
|
from glaip_sdk.cli.transcript import (
|
|
50
52
|
export_cached_transcript,
|
|
51
53
|
load_history_snapshot,
|
|
52
54
|
)
|
|
53
55
|
from glaip_sdk.cli.transcript.viewer import ViewerContext, run_viewer_session
|
|
54
56
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
55
|
-
from glaip_sdk.cli.hints import format_command_hint
|
|
56
57
|
from glaip_sdk.cli.utils import (
|
|
57
58
|
_fuzzy_pick_for_resources,
|
|
58
59
|
command_hint,
|
|
@@ -278,7 +279,11 @@ class SlashSession:
|
|
|
278
279
|
def _ensure_configuration(self) -> bool:
|
|
279
280
|
"""Ensure the CLI has both API URL and credentials before continuing."""
|
|
280
281
|
while not self._configuration_ready():
|
|
281
|
-
self.console.print(
|
|
282
|
+
self.console.print(
|
|
283
|
+
f"[{WARNING_STYLE}]Configuration required.[/] "
|
|
284
|
+
"Slash mode cannot run 'aip accounts ...'. Run setup from your terminal (e.g., "
|
|
285
|
+
"'aip accounts add default' or 'aip configure'), or continue with the `/login` wizard here..."
|
|
286
|
+
)
|
|
282
287
|
self._suppress_login_layout = True
|
|
283
288
|
try:
|
|
284
289
|
self._cmd_login([], False)
|
|
@@ -1285,12 +1290,9 @@ class SlashSession:
|
|
|
1285
1290
|
)
|
|
1286
1291
|
)
|
|
1287
1292
|
|
|
1288
|
-
def _get_api_url(self,
|
|
1289
|
-
"""Get the API URL from
|
|
1290
|
-
|
|
1291
|
-
if isinstance(self.ctx.obj, dict):
|
|
1292
|
-
api_url = self.ctx.obj.get("api_url")
|
|
1293
|
-
return api_url or config.get("api_url") or os.getenv("AIP_API_URL")
|
|
1293
|
+
def _get_api_url(self, _config: dict[str, Any] | None = None) -> str | None:
|
|
1294
|
+
"""Get the API URL from context or account store (CLI/palette ignores env credentials)."""
|
|
1295
|
+
return resolve_api_url_from_context(self.ctx)
|
|
1294
1296
|
|
|
1295
1297
|
def _build_agent_status_line(self, active_agent: Any | None) -> str | None:
|
|
1296
1298
|
"""Return a short status line about the active or recent agent."""
|
|
@@ -12,18 +12,17 @@ from __future__ import annotations
|
|
|
12
12
|
import asyncio
|
|
13
13
|
import json
|
|
14
14
|
import logging
|
|
15
|
+
from collections.abc import Callable
|
|
15
16
|
from dataclasses import dataclass
|
|
16
17
|
from typing import Any
|
|
17
|
-
from collections.abc import Callable
|
|
18
18
|
|
|
19
19
|
from rich.text import Text
|
|
20
|
-
|
|
21
20
|
from textual.app import App, ComposeResult
|
|
22
21
|
from textual.binding import Binding
|
|
23
22
|
from textual.containers import Container, Horizontal
|
|
24
23
|
from textual.reactive import ReactiveError
|
|
25
24
|
from textual.screen import ModalScreen
|
|
26
|
-
from textual.widgets import DataTable, Footer, Header, LoadingIndicator,
|
|
25
|
+
from textual.widgets import DataTable, Footer, Header, LoadingIndicator, RichLog, Static
|
|
27
26
|
|
|
28
27
|
logger = logging.getLogger(__name__)
|
|
29
28
|
|
|
@@ -7,14 +7,13 @@ Authors:
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
|
-
import os
|
|
11
10
|
from dataclasses import dataclass
|
|
12
11
|
from io import StringIO
|
|
13
12
|
from typing import Any
|
|
14
13
|
|
|
15
14
|
from rich.console import Console
|
|
16
15
|
|
|
17
|
-
from glaip_sdk.cli.
|
|
16
|
+
from glaip_sdk.cli.auth import resolve_api_url_from_context
|
|
18
17
|
from glaip_sdk.cli.context import get_ctx_value
|
|
19
18
|
from glaip_sdk.cli.transcript.cache import (
|
|
20
19
|
TranscriptPayload,
|
|
@@ -118,20 +117,12 @@ def register_last_transcript(ctx: Any, payload: TranscriptPayload, store_result:
|
|
|
118
117
|
|
|
119
118
|
|
|
120
119
|
def _resolve_api_url(ctx: Any) -> str | None:
|
|
121
|
-
"""Resolve API URL from context
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if env_url:
|
|
128
|
-
return env_url
|
|
129
|
-
|
|
130
|
-
try:
|
|
131
|
-
config = load_config()
|
|
132
|
-
except Exception:
|
|
133
|
-
return None
|
|
134
|
-
return str(config.get("api_url")) if config.get("api_url") else None
|
|
120
|
+
"""Resolve API URL from context or account store (CLI/palette ignores env creds)."""
|
|
121
|
+
return resolve_api_url_from_context(
|
|
122
|
+
ctx,
|
|
123
|
+
get_api_url=lambda c: get_ctx_value(c, "api_url"),
|
|
124
|
+
get_account_name=lambda c: get_ctx_value(c, "account_name"),
|
|
125
|
+
)
|
|
135
126
|
|
|
136
127
|
|
|
137
128
|
def _extract_step_summaries(renderer: Any) -> list[dict[str, Any]]:
|
|
@@ -300,7 +291,10 @@ def store_transcript_for_session(
|
|
|
300
291
|
|
|
301
292
|
meta, stream_started_at, finished_at, model_name = _derive_transcript_meta(renderer, model)
|
|
302
293
|
|
|
303
|
-
|
|
294
|
+
try:
|
|
295
|
+
api_url = _resolve_api_url(ctx)
|
|
296
|
+
except Exception:
|
|
297
|
+
api_url = None
|
|
304
298
|
if api_url:
|
|
305
299
|
meta["api_url"] = api_url
|
|
306
300
|
|