glaip-sdk 0.6.1__py3-none-any.whl → 0.6.3__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/base.py +71 -23
- glaip_sdk/cli/account_store.py +36 -18
- glaip_sdk/cli/commands/accounts.py +2 -2
- glaip_sdk/cli/slash/accounts_controller.py +308 -25
- glaip_sdk/cli/slash/accounts_shared.py +57 -1
- glaip_sdk/cli/slash/session.py +109 -24
- glaip_sdk/cli/slash/tui/accounts.tcss +33 -1
- glaip_sdk/cli/slash/tui/accounts_app.py +525 -32
- glaip_sdk/cli/slash/tui/remote_runs_app.py +3 -3
- glaip_sdk/client/agents.py +36 -2
- glaip_sdk/utils/runtime_config.py +306 -0
- glaip_sdk/utils/validation.py +3 -3
- {glaip_sdk-0.6.1.dist-info → glaip_sdk-0.6.3.dist-info}/METADATA +1 -1
- {glaip_sdk-0.6.1.dist-info → glaip_sdk-0.6.3.dist-info}/RECORD +16 -15
- {glaip_sdk-0.6.1.dist-info → glaip_sdk-0.6.3.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.1.dist-info → glaip_sdk-0.6.3.dist-info}/entry_points.txt +0 -0
|
@@ -6,14 +6,70 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import os
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
12
|
+
from glaip_sdk.cli.masking import mask_api_key_display
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
def build_account_status_string(row: dict[str, Any], *, use_markup: bool = False) -> str:
|
|
13
|
-
"""Build status string for an account row (active/env-lock).
|
|
16
|
+
"""Build status string for an account row (active/env-lock).
|
|
17
|
+
|
|
18
|
+
When `use_markup` is True, returns Rich markup strings for Textual/Rich rendering;
|
|
19
|
+
when False, returns plain text for console output.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
build_account_status_string({"active": True, "env_lock": True}, use_markup=True)
|
|
23
|
+
returns "[bold green]● active[/] · [yellow]🔒 env-lock[/]"
|
|
24
|
+
use_markup=False returns "● active · 🔒 env-lock"
|
|
25
|
+
"""
|
|
14
26
|
status_parts: list[str] = []
|
|
15
27
|
if row.get("active"):
|
|
16
28
|
status_parts.append("[bold green]● active[/]" if use_markup else "● active")
|
|
17
29
|
if row.get("env_lock"):
|
|
18
30
|
status_parts.append("[yellow]🔒 env-lock[/]" if use_markup else "🔒 env-lock")
|
|
19
31
|
return " · ".join(status_parts)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def env_credentials_present(*, partial: bool = False) -> bool:
|
|
35
|
+
"""Return True when env credentials are present.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
partial: When True, treat either AIP_API_URL or AIP_API_KEY as present
|
|
39
|
+
(used by UIs that should lock on any env override). When False,
|
|
40
|
+
require both to be non-empty (used for context display).
|
|
41
|
+
"""
|
|
42
|
+
api_url = (os.getenv("AIP_API_URL") or "").strip()
|
|
43
|
+
api_key = (os.getenv("AIP_API_KEY") or "").strip()
|
|
44
|
+
if partial:
|
|
45
|
+
return bool(api_url or api_key)
|
|
46
|
+
return bool(api_url and api_key)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_account_rows(
|
|
50
|
+
accounts: dict[str, dict[str, str]],
|
|
51
|
+
active_account: str | None,
|
|
52
|
+
env_lock: bool,
|
|
53
|
+
) -> list[dict[str, str | bool]]:
|
|
54
|
+
"""Build account rows for display from accounts dict.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
accounts: Dictionary mapping account names to account data.
|
|
58
|
+
active_account: Name of the currently active account.
|
|
59
|
+
env_lock: Whether environment credentials are locking account switching.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
List of account row dictionaries with name, api_url, masked_key, active, and env_lock.
|
|
63
|
+
"""
|
|
64
|
+
rows: list[dict[str, str | bool]] = []
|
|
65
|
+
for name, account in sorted(accounts.items()):
|
|
66
|
+
rows.append(
|
|
67
|
+
{
|
|
68
|
+
"name": name,
|
|
69
|
+
"api_url": account.get("api_url", ""),
|
|
70
|
+
"masked_key": mask_api_key_display(account.get("api_key", "")),
|
|
71
|
+
"active": name == active_account,
|
|
72
|
+
"env_lock": env_lock,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
return rows
|
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -41,6 +41,7 @@ from glaip_sdk.cli.commands.update import update_command
|
|
|
41
41
|
from glaip_sdk.cli.hints import format_command_hint
|
|
42
42
|
from glaip_sdk.cli.slash.accounts_controller import AccountsController
|
|
43
43
|
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
44
|
+
from glaip_sdk.cli.slash.accounts_shared import env_credentials_present
|
|
44
45
|
from glaip_sdk.cli.slash.prompt import (
|
|
45
46
|
FormattedText,
|
|
46
47
|
PromptSession,
|
|
@@ -284,39 +285,124 @@ class SlashSession:
|
|
|
284
285
|
if not self.handle_command(raw):
|
|
285
286
|
break
|
|
286
287
|
|
|
288
|
+
def _handle_account_selection(self) -> bool:
|
|
289
|
+
"""Handle account selection when accounts exist but none are active.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
True if configuration is ready after selection, False if user aborted.
|
|
293
|
+
"""
|
|
294
|
+
self.console.print(f"[{INFO_STYLE}]No active account selected. Please choose an account:[/]")
|
|
295
|
+
try:
|
|
296
|
+
self._cmd_accounts([], False)
|
|
297
|
+
self._config_cache = None
|
|
298
|
+
return self._check_configuration_after_selection()
|
|
299
|
+
except KeyboardInterrupt:
|
|
300
|
+
self.console.print(f"[{ERROR_STYLE}]Account selection aborted. Closing the command palette.[/]")
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
def _check_configuration_after_selection(self) -> bool:
|
|
304
|
+
"""Check if configuration is ready after account selection.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
True if configuration is ready, False otherwise.
|
|
308
|
+
"""
|
|
309
|
+
return self._configuration_ready()
|
|
310
|
+
|
|
311
|
+
def _handle_new_account_creation(self) -> bool:
|
|
312
|
+
"""Handle new account creation when no accounts exist.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
True if configuration succeeded, False if user aborted.
|
|
316
|
+
"""
|
|
317
|
+
previous_tip_env = os.environ.get("AIP_SUPPRESS_CONFIGURE_TIP")
|
|
318
|
+
os.environ["AIP_SUPPRESS_CONFIGURE_TIP"] = "1"
|
|
319
|
+
self._suppress_login_layout = True
|
|
320
|
+
try:
|
|
321
|
+
self._cmd_login([], False)
|
|
322
|
+
return True
|
|
323
|
+
except KeyboardInterrupt:
|
|
324
|
+
self.console.print(f"[{ERROR_STYLE}]Configuration aborted. Closing the command palette.[/]")
|
|
325
|
+
return False
|
|
326
|
+
finally:
|
|
327
|
+
self._suppress_login_layout = False
|
|
328
|
+
if previous_tip_env is None:
|
|
329
|
+
os.environ.pop("AIP_SUPPRESS_CONFIGURE_TIP", None)
|
|
330
|
+
else:
|
|
331
|
+
os.environ["AIP_SUPPRESS_CONFIGURE_TIP"] = previous_tip_env
|
|
332
|
+
|
|
287
333
|
def _ensure_configuration(self) -> bool:
|
|
288
334
|
"""Ensure the CLI has both API URL and credentials before continuing."""
|
|
289
335
|
while not self._configuration_ready():
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
self.
|
|
336
|
+
store = get_account_store()
|
|
337
|
+
accounts = store.list_accounts()
|
|
338
|
+
active_account = store.get_active_account()
|
|
339
|
+
|
|
340
|
+
# If accounts exist but none are active, show accounts list first
|
|
341
|
+
if accounts and (not active_account or active_account not in accounts):
|
|
342
|
+
if not self._handle_account_selection():
|
|
343
|
+
return False
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
# No accounts exist - prompt for configuration
|
|
347
|
+
if not self._handle_new_account_creation():
|
|
297
348
|
return False
|
|
298
|
-
finally:
|
|
299
|
-
self._suppress_login_layout = False
|
|
300
|
-
if previous_tip_env is None:
|
|
301
|
-
os.environ.pop("AIP_SUPPRESS_CONFIGURE_TIP", None)
|
|
302
|
-
else:
|
|
303
|
-
os.environ["AIP_SUPPRESS_CONFIGURE_TIP"] = previous_tip_env
|
|
304
349
|
|
|
305
350
|
return True
|
|
306
351
|
|
|
352
|
+
def _get_credentials_from_context_and_env(self) -> tuple[str, str]:
|
|
353
|
+
"""Get credentials from context and environment variables.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Tuple of (api_url, api_key) from context/env overrides.
|
|
357
|
+
"""
|
|
358
|
+
api_url = ""
|
|
359
|
+
api_key = ""
|
|
360
|
+
if isinstance(self.ctx.obj, dict):
|
|
361
|
+
api_url = self.ctx.obj.get("api_url", "")
|
|
362
|
+
api_key = self.ctx.obj.get("api_key", "")
|
|
363
|
+
# Environment variables take precedence
|
|
364
|
+
env_url = os.getenv("AIP_API_URL", "")
|
|
365
|
+
env_key = os.getenv("AIP_API_KEY", "")
|
|
366
|
+
return (env_url or api_url, env_key or api_key)
|
|
367
|
+
|
|
368
|
+
def _get_credentials_from_account_store(self) -> tuple[str, str] | None:
|
|
369
|
+
"""Get credentials from the active account in account store.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Tuple of (api_url, api_key) if active account exists, None otherwise.
|
|
373
|
+
"""
|
|
374
|
+
store = get_account_store()
|
|
375
|
+
active_account = store.get_active_account()
|
|
376
|
+
if not active_account:
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
account = store.get_account(active_account)
|
|
380
|
+
if not account:
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
api_url = account.get("api_url", "")
|
|
384
|
+
api_key = account.get("api_key", "")
|
|
385
|
+
return (api_url, api_key)
|
|
386
|
+
|
|
307
387
|
def _configuration_ready(self) -> bool:
|
|
308
388
|
"""Check whether API URL and credentials are available."""
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if
|
|
389
|
+
# Check for explicit overrides in context/env first
|
|
390
|
+
override_url, override_key = self._get_credentials_from_context_and_env()
|
|
391
|
+
if override_url and override_key:
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
# Read from account store directly to avoid stale cache
|
|
395
|
+
account_creds = self._get_credentials_from_account_store()
|
|
396
|
+
if account_creds is None:
|
|
312
397
|
return False
|
|
313
398
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
399
|
+
store_url, store_key = account_creds
|
|
400
|
+
|
|
401
|
+
# Use override values if available, otherwise use store values
|
|
402
|
+
api_url = override_url or store_url
|
|
403
|
+
api_key = override_key or store_key
|
|
317
404
|
|
|
318
|
-
|
|
319
|
-
return bool(api_key)
|
|
405
|
+
return bool(api_url and api_key)
|
|
320
406
|
|
|
321
407
|
def handle_command(self, raw: str, *, invoked_from_agent: bool = False) -> bool:
|
|
322
408
|
"""Parse and execute a single slash command string."""
|
|
@@ -1339,11 +1425,10 @@ class SlashSession:
|
|
|
1339
1425
|
host = ""
|
|
1340
1426
|
if account:
|
|
1341
1427
|
host = account.get("api_url", "")
|
|
1342
|
-
env_lock =
|
|
1428
|
+
env_lock = env_credentials_present()
|
|
1343
1429
|
return active, host, env_lock
|
|
1344
1430
|
except Exception:
|
|
1345
|
-
|
|
1346
|
-
return "default", "", env_lock
|
|
1431
|
+
return "default", "", env_credentials_present()
|
|
1347
1432
|
|
|
1348
1433
|
def _build_agent_status_line(self, active_agent: Any | None) -> str | None:
|
|
1349
1434
|
"""Return a short status line about the active or recent agent."""
|
|
@@ -19,16 +19,26 @@
|
|
|
19
19
|
padding: 0 1 0 1;
|
|
20
20
|
margin: 0 0 0 0;
|
|
21
21
|
height: auto;
|
|
22
|
+
align: center middle;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
#filter-label {
|
|
25
|
-
height:
|
|
26
|
+
height: 3;
|
|
27
|
+
width: 12;
|
|
28
|
+
content-align: left middle;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
#filter-input {
|
|
29
32
|
padding: 0 1 0 1;
|
|
30
33
|
margin: 0;
|
|
31
34
|
height: 3;
|
|
35
|
+
width: 1fr;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#filter-clear {
|
|
39
|
+
padding: 0 1;
|
|
40
|
+
min-width: 12;
|
|
41
|
+
margin-left: 1;
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
#accounts-table {
|
|
@@ -52,3 +62,25 @@
|
|
|
52
62
|
margin: 0;
|
|
53
63
|
color: cyan;
|
|
54
64
|
}
|
|
65
|
+
|
|
66
|
+
.form-label {
|
|
67
|
+
padding: 0 1 0 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#form-actions, #form-key-actions, #confirm-actions {
|
|
71
|
+
padding: 0 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#form-status, #confirm-status {
|
|
75
|
+
padding: 0 1;
|
|
76
|
+
color: yellow;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#form-test {
|
|
80
|
+
margin: 0 1 0 1;
|
|
81
|
+
padding: 0 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#form-actions {
|
|
85
|
+
margin: 0 1 0 1;
|
|
86
|
+
}
|