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.
@@ -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
@@ -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
- previous_tip_env = os.environ.get("AIP_SUPPRESS_CONFIGURE_TIP")
291
- os.environ["AIP_SUPPRESS_CONFIGURE_TIP"] = "1"
292
- self._suppress_login_layout = True
293
- try:
294
- self._cmd_login([], False)
295
- except KeyboardInterrupt:
296
- self.console.print(f"[{ERROR_STYLE}]Configuration aborted. Closing the command palette.[/]")
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
- config = self._load_config()
310
- api_url = self._get_api_url(config)
311
- if not api_url:
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
- api_key: str | None = None
315
- if isinstance(self.ctx.obj, dict):
316
- api_key = self.ctx.obj.get("api_key")
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
- api_key = api_key or config.get("api_key") or os.getenv("AIP_API_KEY")
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 = bool(os.getenv("AIP_API_URL") or os.getenv("AIP_API_KEY"))
1428
+ env_lock = env_credentials_present()
1343
1429
  return active, host, env_lock
1344
1430
  except Exception:
1345
- env_lock = bool(os.getenv("AIP_API_URL") or os.getenv("AIP_API_KEY"))
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: 1;
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
+ }