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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Shared helpers for configuration/account flows."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.text import Text
|
|
6
|
+
|
|
7
|
+
from glaip_sdk import Client
|
|
8
|
+
from glaip_sdk.branding import PRIMARY, SUCCESS_STYLE, WARNING_STYLE, AIPBranding
|
|
9
|
+
from glaip_sdk.cli.utils import sdk_version
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def render_branding_header(console: Console, rule_text: str) -> None:
|
|
13
|
+
"""Render the standard CLI branding header with a custom rule text."""
|
|
14
|
+
branding = AIPBranding.create_from_sdk(sdk_version=sdk_version(), package_name="glaip-sdk")
|
|
15
|
+
heading = "[bold]>_ GDP Labs AI Agents Package (AIP CLI)[/bold]"
|
|
16
|
+
console.print(heading)
|
|
17
|
+
console.print()
|
|
18
|
+
console.print(branding.get_welcome_banner())
|
|
19
|
+
console.rule(rule_text, style=PRIMARY)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_connection(
|
|
23
|
+
api_url: str,
|
|
24
|
+
api_key: str,
|
|
25
|
+
console: Console,
|
|
26
|
+
*,
|
|
27
|
+
abort_on_error: bool = False,
|
|
28
|
+
extra_hint: str | None = None,
|
|
29
|
+
) -> bool:
|
|
30
|
+
"""Test connectivity and report results.
|
|
31
|
+
|
|
32
|
+
Returns True on success, False on handled failures. Raises click.Abort when
|
|
33
|
+
abort_on_error is True and a fatal error occurs.
|
|
34
|
+
"""
|
|
35
|
+
console.print("\n🔌 Testing connection...")
|
|
36
|
+
client: Client | None = None
|
|
37
|
+
try:
|
|
38
|
+
# Import lazily so test patches targeting glaip_sdk.Client are honored
|
|
39
|
+
from importlib import import_module # noqa: PLC0415
|
|
40
|
+
|
|
41
|
+
client_module = import_module("glaip_sdk")
|
|
42
|
+
client = client_module.Client(api_url=api_url, api_key=api_key)
|
|
43
|
+
try:
|
|
44
|
+
agents = client.list_agents()
|
|
45
|
+
console.print(Text(f"✅ Connection successful! Found {len(agents)} agents", style=SUCCESS_STYLE))
|
|
46
|
+
return True
|
|
47
|
+
except Exception as exc: # pragma: no cover - API failures depend on network
|
|
48
|
+
console.print(Text(f"⚠️ Connection established but API call failed: {exc}", style=WARNING_STYLE))
|
|
49
|
+
console.print(" You may need to check your API permissions or network access")
|
|
50
|
+
if extra_hint:
|
|
51
|
+
console.print(extra_hint)
|
|
52
|
+
if abort_on_error:
|
|
53
|
+
raise click.Abort() from exc
|
|
54
|
+
return False
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
console.print(Text(f"❌ Connection failed: {exc}"))
|
|
57
|
+
console.print(" Please check your API URL and key")
|
|
58
|
+
if extra_hint:
|
|
59
|
+
console.print(extra_hint)
|
|
60
|
+
if abort_on_error:
|
|
61
|
+
raise click.Abort() from exc
|
|
62
|
+
return False
|
|
63
|
+
finally:
|
|
64
|
+
if client is not None:
|
|
65
|
+
client.close()
|
|
@@ -10,43 +10,53 @@ import click
|
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
from rich.text import Text
|
|
12
12
|
|
|
13
|
-
from glaip_sdk import
|
|
14
|
-
from glaip_sdk.
|
|
15
|
-
|
|
16
|
-
ERROR_STYLE,
|
|
17
|
-
INFO,
|
|
18
|
-
PRIMARY,
|
|
19
|
-
SUCCESS,
|
|
20
|
-
SUCCESS_STYLE,
|
|
21
|
-
WARNING_STYLE,
|
|
22
|
-
AIPBranding,
|
|
23
|
-
)
|
|
13
|
+
from glaip_sdk.branding import ACCENT_STYLE, ERROR_STYLE, INFO, SUCCESS, SUCCESS_STYLE, WARNING_STYLE
|
|
14
|
+
from glaip_sdk.cli.account_store import get_account_store
|
|
15
|
+
from glaip_sdk.cli.commands.common_config import check_connection, render_branding_header
|
|
24
16
|
from glaip_sdk.cli.config import CONFIG_FILE, load_config, save_config
|
|
25
|
-
from glaip_sdk.cli.rich_helpers import markup_text
|
|
26
17
|
from glaip_sdk.cli.hints import format_command_hint
|
|
27
|
-
from glaip_sdk.cli.
|
|
18
|
+
from glaip_sdk.cli.masking import mask_api_key_display
|
|
19
|
+
from glaip_sdk.cli.rich_helpers import markup_text
|
|
20
|
+
from glaip_sdk.cli.utils import command_hint
|
|
28
21
|
from glaip_sdk.icons import ICON_TOOL
|
|
29
22
|
from glaip_sdk.rich_components import AIPTable
|
|
30
23
|
|
|
31
24
|
console = Console()
|
|
32
25
|
|
|
26
|
+
# Shared deprecation banner for legacy config commands
|
|
27
|
+
CONFIG_DEPRECATION_MSG = (
|
|
28
|
+
f"[{WARNING_STYLE}]Deprecated: 'aip config ...' will be removed in a future release. "
|
|
29
|
+
"Use 'aip accounts ...' (list/add/use/remove) or 'aip configure' for the wizard.[/]"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print_config_deprecation() -> None:
|
|
34
|
+
"""Print a standardized deprecation warning for legacy config commands."""
|
|
35
|
+
console.print(CONFIG_DEPRECATION_MSG)
|
|
36
|
+
|
|
33
37
|
|
|
34
38
|
@click.group()
|
|
35
39
|
def config_group() -> None:
|
|
36
40
|
"""Configuration management operations."""
|
|
37
|
-
|
|
41
|
+
_print_config_deprecation()
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
@config_group.command("list")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
@click.option("--json", "output_json", is_flag=True, help="Output in JSON format")
|
|
46
|
+
@click.pass_context
|
|
47
|
+
def list_config(ctx: click.Context, output_json: bool) -> None:
|
|
48
|
+
"""List current configuration.
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
Deprecated: run 'aip accounts list' for profile-aware output.
|
|
51
|
+
"""
|
|
52
|
+
console.print(f"[{WARNING_STYLE}]Deprecated: run 'aip accounts list' for profile-aware output.[/]")
|
|
53
|
+
|
|
54
|
+
# Delegate to accounts list by invoking the command
|
|
55
|
+
from glaip_sdk.cli.commands.accounts import accounts_group # noqa: PLC0415
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
list_cmd = accounts_group.get_command(ctx, "list")
|
|
58
|
+
if list_cmd:
|
|
59
|
+
ctx.invoke(list_cmd, output_json=output_json)
|
|
50
60
|
|
|
51
61
|
|
|
52
62
|
CONFIG_VALUE_TYPES: dict[str, str] = {
|
|
@@ -104,14 +114,49 @@ def _coerce_config_value(key: str, raw_value: str) -> str | bool | int | float:
|
|
|
104
114
|
@config_group.command("set")
|
|
105
115
|
@click.argument("key")
|
|
106
116
|
@click.argument("value")
|
|
107
|
-
|
|
108
|
-
""
|
|
109
|
-
|
|
117
|
+
@click.option(
|
|
118
|
+
"--account",
|
|
119
|
+
"account_name",
|
|
120
|
+
help="Account name to set value for (defaults to active account)",
|
|
121
|
+
)
|
|
122
|
+
def set_config(key: str, value: str, account_name: str | None) -> None:
|
|
123
|
+
"""Set a configuration value.
|
|
110
124
|
|
|
125
|
+
For api_url and api_key, this operates on the specified account (or active account).
|
|
126
|
+
Other keys (timeout, history_default_limit) are global settings.
|
|
127
|
+
"""
|
|
128
|
+
# For other keys, use legacy config
|
|
129
|
+
valid_keys = tuple(CONFIG_VALUE_TYPES.keys())
|
|
111
130
|
if key not in valid_keys:
|
|
112
131
|
console.print(f"[{ERROR_STYLE}]Error: Invalid key '{key}'. Valid keys are: {', '.join(valid_keys)}[/]")
|
|
113
132
|
raise click.ClickException(f"Invalid configuration key: {key}")
|
|
114
133
|
|
|
134
|
+
store = get_account_store()
|
|
135
|
+
# For api_url and api_key, update account profile but also mirror to legacy config
|
|
136
|
+
if key in ("api_url", "api_key"):
|
|
137
|
+
target_account = account_name or store.get_active_account() or "default"
|
|
138
|
+
try:
|
|
139
|
+
account = store.get_account(target_account) or {}
|
|
140
|
+
account[key] = value
|
|
141
|
+
store.add_account(
|
|
142
|
+
target_account,
|
|
143
|
+
account.get("api_url", ""),
|
|
144
|
+
account.get("api_key", ""),
|
|
145
|
+
overwrite=True,
|
|
146
|
+
)
|
|
147
|
+
except Exception:
|
|
148
|
+
# If account store persistence fails (e.g., mocked I/O), continue with legacy config
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Always update legacy config for backward compatibility and test isolation
|
|
152
|
+
legacy_config = load_config()
|
|
153
|
+
legacy_config[key] = value
|
|
154
|
+
save_config(legacy_config)
|
|
155
|
+
|
|
156
|
+
display_value = _mask_api_key(value) if key == "api_key" else value
|
|
157
|
+
console.print(Text(f"✅ Set {key} = {display_value} for account '{target_account}'", style=SUCCESS_STYLE))
|
|
158
|
+
return
|
|
159
|
+
|
|
115
160
|
coerced_value = _coerce_config_value(key, value)
|
|
116
161
|
config = load_config()
|
|
117
162
|
config[key] = coerced_value
|
|
@@ -127,12 +172,19 @@ def get_config(key: str) -> None:
|
|
|
127
172
|
"""Get a configuration value."""
|
|
128
173
|
config = load_config()
|
|
129
174
|
|
|
130
|
-
|
|
175
|
+
value = config.get(key)
|
|
176
|
+
|
|
177
|
+
# Fallback to account store for api_url/api_key when legacy config lacks the key
|
|
178
|
+
if value is None and key in {"api_url", "api_key"}:
|
|
179
|
+
store = get_account_store()
|
|
180
|
+
active = store.get_active_account() or "default"
|
|
181
|
+
account = store.get_account(active) or {}
|
|
182
|
+
value = account.get(key)
|
|
183
|
+
|
|
184
|
+
if value is None:
|
|
131
185
|
console.print(markup_text(f"[{WARNING_STYLE}]Configuration key '{key}' not found.[/]"))
|
|
132
186
|
raise click.ClickException(f"Configuration key not found: {key}")
|
|
133
187
|
|
|
134
|
-
value = config[key]
|
|
135
|
-
|
|
136
188
|
if key == "api_key":
|
|
137
189
|
console.print(_mask_api_key(value))
|
|
138
190
|
else:
|
|
@@ -190,41 +242,73 @@ def reset_config(force: bool) -> None:
|
|
|
190
242
|
console.print(message)
|
|
191
243
|
|
|
192
244
|
|
|
193
|
-
def _configure_interactive() -> None:
|
|
245
|
+
def _configure_interactive(account_name: str | None = None) -> None:
|
|
194
246
|
"""Shared configuration logic for both configure commands."""
|
|
247
|
+
store = get_account_store()
|
|
248
|
+
|
|
249
|
+
# Determine account name (use provided, active, or default)
|
|
250
|
+
if not account_name:
|
|
251
|
+
account_name = store.get_active_account() or "default"
|
|
252
|
+
|
|
253
|
+
# Get existing account if it exists
|
|
254
|
+
existing = store.get_account(account_name)
|
|
255
|
+
|
|
195
256
|
_render_configuration_header()
|
|
196
|
-
config =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
257
|
+
config = _prompt_configuration_inputs_for_account(existing)
|
|
258
|
+
|
|
259
|
+
# Save to account store
|
|
260
|
+
api_url = config.get("api_url", "")
|
|
261
|
+
api_key = config.get("api_key", "")
|
|
262
|
+
if api_url and api_key:
|
|
263
|
+
store.add_account(account_name, api_url, api_key, overwrite=True)
|
|
264
|
+
console.print(Text(f"\n✅ Configuration saved to account '{account_name}'", style=SUCCESS_STYLE))
|
|
265
|
+
|
|
266
|
+
_test_and_report_connection_for_account(account_name)
|
|
200
267
|
_print_post_configuration_hints()
|
|
268
|
+
# Show active account footer
|
|
269
|
+
from glaip_sdk.cli.commands.accounts import _print_active_account_footer # noqa: PLC0415
|
|
270
|
+
|
|
271
|
+
_print_active_account_footer(store)
|
|
201
272
|
|
|
202
273
|
|
|
203
274
|
@config_group.command()
|
|
204
|
-
|
|
205
|
-
""
|
|
206
|
-
|
|
275
|
+
@click.option(
|
|
276
|
+
"--account",
|
|
277
|
+
"account_name",
|
|
278
|
+
help="Account name to configure (defaults to active account)",
|
|
279
|
+
)
|
|
280
|
+
def configure(account_name: str | None) -> None:
|
|
281
|
+
"""Configure AIP CLI credentials and settings interactively.
|
|
282
|
+
|
|
283
|
+
This command is an alias for 'aip accounts add <name>' and will
|
|
284
|
+
configure the specified account (or active account if not specified).
|
|
285
|
+
"""
|
|
286
|
+
_configure_interactive(account_name)
|
|
207
287
|
|
|
208
288
|
|
|
209
289
|
# Alias command for backward compatibility
|
|
210
290
|
@click.command()
|
|
211
|
-
|
|
291
|
+
@click.option(
|
|
292
|
+
"--account",
|
|
293
|
+
"account_name",
|
|
294
|
+
help="Account name to configure (defaults to active account)",
|
|
295
|
+
)
|
|
296
|
+
def configure_command(account_name: str | None) -> None:
|
|
212
297
|
"""Configure AIP CLI credentials and settings interactively.
|
|
213
298
|
|
|
214
299
|
This is an alias for 'aip config configure' for backward compatibility.
|
|
300
|
+
For multi-account support, use 'aip accounts add <name>' instead.
|
|
215
301
|
"""
|
|
302
|
+
console.print(
|
|
303
|
+
f"[{WARNING_STYLE}]Setup tip:[/] Prefer 'aip accounts add <name>' or 'aip configure' from your terminal for "
|
|
304
|
+
"multi-account setup. Launching the interactive wizard now..."
|
|
305
|
+
)
|
|
216
306
|
# Delegate to the shared function
|
|
217
|
-
_configure_interactive()
|
|
307
|
+
_configure_interactive(account_name)
|
|
218
308
|
|
|
219
309
|
|
|
220
310
|
# Note: The config command group should be registered in main.py
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _mask_api_key(value: str | None) -> str:
|
|
224
|
-
"""Return a redacted API key string suitable for display."""
|
|
225
|
-
if not value:
|
|
226
|
-
return ""
|
|
227
|
-
return "***" + value[-4:] if len(value) > 4 else "***"
|
|
311
|
+
_mask_api_key = mask_api_key_display
|
|
228
312
|
|
|
229
313
|
|
|
230
314
|
def _print_missing_config_hint() -> None:
|
|
@@ -251,23 +335,23 @@ def _render_config_table(config: dict[str, str]) -> None:
|
|
|
251
335
|
|
|
252
336
|
def _render_configuration_header() -> None:
|
|
253
337
|
"""Display the interactive configuration heading/banner."""
|
|
254
|
-
|
|
255
|
-
heading = "[bold]>_ GDP Labs AI Agents Package (AIP CLI)[/bold]"
|
|
256
|
-
console.print(heading)
|
|
257
|
-
console.print()
|
|
258
|
-
console.print(branding.get_welcome_banner())
|
|
259
|
-
console.rule("[bold]AIP Configuration[/bold]", style=PRIMARY)
|
|
338
|
+
render_branding_header(console, "[bold]AIP Configuration[/bold]")
|
|
260
339
|
|
|
261
340
|
|
|
262
|
-
def
|
|
263
|
-
"""Interactively prompt for configuration values."""
|
|
341
|
+
def _prompt_configuration_inputs_for_account(existing: dict[str, str] | None) -> dict[str, str]:
|
|
342
|
+
"""Interactively prompt for account configuration values."""
|
|
264
343
|
console.print("\n[bold]Enter your AIP configuration:[/bold]")
|
|
265
|
-
|
|
344
|
+
if existing:
|
|
345
|
+
console.print("(Leave blank to keep current values)")
|
|
266
346
|
console.print("─" * 50)
|
|
267
347
|
|
|
348
|
+
config = existing.copy() if existing else {}
|
|
349
|
+
|
|
268
350
|
_prompt_api_url(config)
|
|
269
351
|
_prompt_api_key(config)
|
|
270
352
|
|
|
353
|
+
return config
|
|
354
|
+
|
|
271
355
|
|
|
272
356
|
def _prompt_api_url(config: dict[str, str]) -> None:
|
|
273
357
|
"""Ask the user for the API URL, preserving existing values by default."""
|
|
@@ -291,43 +375,24 @@ def _prompt_api_key(config: dict[str, str]) -> None:
|
|
|
291
375
|
config["api_key"] = new_key
|
|
292
376
|
|
|
293
377
|
|
|
294
|
-
def
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
|
-
|
|
378
|
+
def _test_and_report_connection_for_account(account_name: str) -> None:
|
|
379
|
+
"""Sanity-check the provided credentials against the backend."""
|
|
380
|
+
store = get_account_store()
|
|
381
|
+
account = store.get_account(account_name)
|
|
382
|
+
if not account:
|
|
383
|
+
return
|
|
298
384
|
|
|
385
|
+
api_url = account.get("api_url", "")
|
|
386
|
+
api_key = account.get("api_key", "")
|
|
387
|
+
if not api_url or not api_key:
|
|
388
|
+
return
|
|
299
389
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
try:
|
|
307
|
-
agents = client.list_agents()
|
|
308
|
-
console.print(
|
|
309
|
-
Text(
|
|
310
|
-
f"✅ Connection successful! Found {len(agents)} agents",
|
|
311
|
-
style=SUCCESS_STYLE,
|
|
312
|
-
)
|
|
313
|
-
)
|
|
314
|
-
except Exception as exc: # pragma: no cover - API failures depend on network
|
|
315
|
-
console.print(
|
|
316
|
-
Text(
|
|
317
|
-
f"⚠️ Connection established but API call failed: {exc}",
|
|
318
|
-
style=WARNING_STYLE,
|
|
319
|
-
)
|
|
320
|
-
)
|
|
321
|
-
console.print(" You may need to check your API permissions or network access")
|
|
322
|
-
except Exception as exc:
|
|
323
|
-
console.print(Text(f"❌ Connection failed: {exc}"))
|
|
324
|
-
console.print(" Please check your API URL and key")
|
|
325
|
-
hint_status = command_hint("status", slash_command="status")
|
|
326
|
-
if hint_status:
|
|
327
|
-
console.print(f" You can run {format_command_hint(hint_status) or hint_status} later to test again")
|
|
328
|
-
finally:
|
|
329
|
-
if client is not None:
|
|
330
|
-
client.close()
|
|
390
|
+
hint_status = command_hint("status", slash_command="status")
|
|
391
|
+
extra_hint = None
|
|
392
|
+
if hint_status:
|
|
393
|
+
extra_hint = f" You can run {format_command_hint(hint_status) or hint_status} later to test again"
|
|
394
|
+
|
|
395
|
+
check_connection(api_url, api_key, console, abort_on_error=False, extra_hint=extra_hint)
|
|
331
396
|
|
|
332
397
|
|
|
333
398
|
def _print_post_configuration_hints() -> None:
|
|
@@ -36,8 +36,8 @@ from glaip_sdk.cli.transcript.history import (
|
|
|
36
36
|
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
|
-
from glaip_sdk.utils.rendering.renderer.debug import render_debug_event
|
|
40
39
|
from glaip_sdk.utils.rendering.layout.panels import create_final_panel
|
|
40
|
+
from glaip_sdk.utils.rendering.renderer.debug import render_debug_event
|
|
41
41
|
|
|
42
42
|
console = Console()
|
|
43
43
|
|
glaip_sdk/cli/config.py
CHANGED
|
@@ -5,12 +5,25 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
from copy import deepcopy
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
import yaml
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
_ENV_CONFIG_DIR = os.getenv("AIP_CONFIG_DIR")
|
|
15
|
+
_TEST_ENV = os.getenv("PYTEST_CURRENT_TEST") or os.getenv("PYTEST_XDIST_WORKER")
|
|
16
|
+
|
|
17
|
+
if _ENV_CONFIG_DIR:
|
|
18
|
+
CONFIG_DIR = Path(_ENV_CONFIG_DIR)
|
|
19
|
+
elif _TEST_ENV:
|
|
20
|
+
# Isolate test runs (including xdist workers) from the real user config directory
|
|
21
|
+
import tempfile
|
|
22
|
+
|
|
23
|
+
CONFIG_DIR = Path(tempfile.gettempdir()) / "aip-test-config"
|
|
24
|
+
else: # pragma: no cover - default path used outside test runs
|
|
25
|
+
CONFIG_DIR = Path.home() / ".aip"
|
|
26
|
+
|
|
14
27
|
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
15
28
|
_ALLOWED_KEYS = {
|
|
16
29
|
"api_url",
|
|
@@ -18,13 +31,28 @@ _ALLOWED_KEYS = {
|
|
|
18
31
|
"timeout",
|
|
19
32
|
"history_default_limit",
|
|
20
33
|
}
|
|
34
|
+
# Keys that must be preserved for multi-account support
|
|
35
|
+
_PRESERVE_KEYS = {
|
|
36
|
+
"version",
|
|
37
|
+
"active_account",
|
|
38
|
+
"accounts",
|
|
39
|
+
}
|
|
21
40
|
|
|
22
41
|
|
|
23
42
|
def _sanitize_config(data: dict[str, Any] | None) -> dict[str, Any]:
|
|
24
|
-
"""Return config filtered to allowed keys only."""
|
|
43
|
+
"""Return config filtered to allowed keys only, preserving multi-account keys."""
|
|
25
44
|
if not data:
|
|
26
45
|
return {}
|
|
27
|
-
|
|
46
|
+
result: dict[str, Any] = {}
|
|
47
|
+
# Preserve multi-account structure (defensively copy to avoid callers mutating source)
|
|
48
|
+
for key in _PRESERVE_KEYS:
|
|
49
|
+
if key in data:
|
|
50
|
+
result[key] = deepcopy(data[key])
|
|
51
|
+
# Add allowed legacy keys (copied to avoid side effects)
|
|
52
|
+
for key in _ALLOWED_KEYS:
|
|
53
|
+
if key in data:
|
|
54
|
+
result[key] = deepcopy(data[key])
|
|
55
|
+
return result
|
|
28
56
|
|
|
29
57
|
|
|
30
58
|
def load_config() -> dict[str, Any]:
|
glaip_sdk/cli/display.py
CHANGED
|
@@ -16,8 +16,8 @@ from rich.panel import Panel
|
|
|
16
16
|
from rich.text import Text
|
|
17
17
|
|
|
18
18
|
from glaip_sdk.branding import ERROR_STYLE, SUCCESS, SUCCESS_STYLE, WARNING_STYLE
|
|
19
|
-
from glaip_sdk.cli.rich_helpers import markup_text
|
|
20
19
|
from glaip_sdk.cli.hints import command_hint, format_command_hint, in_slash_mode
|
|
20
|
+
from glaip_sdk.cli.rich_helpers import markup_text
|
|
21
21
|
from glaip_sdk.icons import ICON_AGENT, ICON_TOOL
|
|
22
22
|
from glaip_sdk.rich_components import AIPPanel
|
|
23
23
|
|