glaip-sdk 0.5.3__py3-none-any.whl → 0.5.5__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,7 +6,9 @@ Authors:
6
6
 
7
7
  import getpass
8
8
  import json
9
+ import os
9
10
  import sys
11
+ from pathlib import Path
10
12
 
11
13
  import click
12
14
  from rich.console import Console
@@ -110,6 +112,153 @@ def list_accounts(output_json: bool) -> None:
110
112
  console.print(f"\n[{INFO}]💡 Tip[/]: To update an account's URL or key, use: [bold]aip accounts edit <name>[/bold]")
111
113
 
112
114
 
115
+ def _build_account_json_payload(
116
+ name: str,
117
+ api_url: str,
118
+ masked_key: str,
119
+ config_path: str,
120
+ is_active: bool,
121
+ env_lock: bool,
122
+ metadata: dict[str, str | None],
123
+ ) -> dict[str, str | bool | None]:
124
+ """Build JSON payload for account display.
125
+
126
+ Args:
127
+ name: Account name.
128
+ api_url: API URL.
129
+ masked_key: Masked API key.
130
+ config_path: Config file path.
131
+ is_active: Whether account is active.
132
+ env_lock: Whether env credentials are set.
133
+ metadata: Account metadata dict.
134
+
135
+ Returns:
136
+ JSON payload dict.
137
+ """
138
+ payload: dict[str, str | bool | None] = {
139
+ "name": name,
140
+ "api_url": api_url,
141
+ "api_key_masked": masked_key,
142
+ "config_path": config_path,
143
+ "active": is_active,
144
+ "env_lock": env_lock,
145
+ }
146
+ for key, value in metadata.items():
147
+ if value:
148
+ payload[key] = value
149
+ return payload
150
+
151
+
152
+ def _format_config_path(config_path: str) -> str:
153
+ """Format config path for display, shortening under home."""
154
+ path_obj = Path(config_path).expanduser()
155
+ try:
156
+ home = Path.home().expanduser()
157
+ resolved = path_obj.resolve(strict=False)
158
+ relative = resolved.relative_to(home).as_posix()
159
+ return f"~/{relative}"
160
+ except ValueError:
161
+ # Not under home; return expanded path
162
+ return str(path_obj)
163
+ except OSError:
164
+ # Fall back to original string on resolution errors
165
+ return config_path
166
+
167
+
168
+ def _build_account_display_lines(
169
+ name: str,
170
+ api_url: str,
171
+ masked_key: str,
172
+ config_path: str,
173
+ is_active: bool,
174
+ env_lock: bool,
175
+ metadata: dict[str, str | None],
176
+ ) -> list[str]:
177
+ """Build display lines for account information.
178
+
179
+ Args:
180
+ name: Account name.
181
+ api_url: API URL.
182
+ masked_key: Masked API key.
183
+ config_path: Config file path.
184
+ is_active: Whether account is active.
185
+ env_lock: Whether env credentials are set.
186
+ metadata: Account metadata dict.
187
+
188
+ Returns:
189
+ List of formatted display lines.
190
+ """
191
+ lines = [
192
+ f"[{SUCCESS_STYLE}]Name[/]: {name}{' (active)' if is_active else ''}",
193
+ f"[{SUCCESS_STYLE}]API URL[/]: {api_url or 'not set'}",
194
+ f"[{SUCCESS_STYLE}]Key[/]: {masked_key or 'not set'}",
195
+ f"[{SUCCESS_STYLE}]Config[/]: {config_path}",
196
+ ]
197
+
198
+ label_map = {
199
+ "notes": "Notes",
200
+ "last_used_at": "Last used",
201
+ "last_validated_at": "Last validated",
202
+ "created_with": "Created with",
203
+ }
204
+ for key, label in label_map.items():
205
+ value = metadata.get(key)
206
+ if value:
207
+ lines.append(f"[{SUCCESS_STYLE}]{label}[/]: {value}")
208
+
209
+ if env_lock:
210
+ lines.append(
211
+ f"[{WARNING_STYLE}]Env credentials detected (AIP_API_URL/AIP_API_KEY); stored profile may be ignored.[/]"
212
+ )
213
+
214
+ return lines
215
+
216
+
217
+ @accounts_group.command("show")
218
+ @click.argument("name")
219
+ @click.option("--json", "output_json", is_flag=True, help="Output in JSON format")
220
+ def show_account(name: str, output_json: bool) -> None:
221
+ """Show details for a single account profile."""
222
+ store = get_account_store()
223
+ account = store.get_account(name)
224
+
225
+ if not account:
226
+ console.print(f"[{ERROR_STYLE}]Error: Account '{name}' not found.[/]")
227
+ raise click.Abort()
228
+
229
+ api_url = account.get("api_url", "")
230
+ api_key = account.get("api_key")
231
+ masked_key = _mask_api_key(api_key or "")
232
+ active_account = store.get_active_account()
233
+ is_active = active_account == name
234
+ env_lock = bool(os.getenv("AIP_API_URL") or os.getenv("AIP_API_KEY"))
235
+ config_path_raw = str(store.config_file)
236
+ config_path_display = _format_config_path(config_path_raw)
237
+
238
+ metadata = {
239
+ "notes": account.get("notes"),
240
+ "last_used_at": account.get("last_used_at"),
241
+ "last_validated_at": account.get("last_validated_at"),
242
+ "created_with": account.get("created_with"),
243
+ }
244
+
245
+ if output_json:
246
+ payload = _build_account_json_payload(name, api_url, masked_key, config_path_raw, is_active, env_lock, metadata)
247
+ click.echo(json.dumps(payload, indent=2))
248
+ return
249
+
250
+ lines = _build_account_display_lines(name, api_url, masked_key, config_path_display, is_active, env_lock, metadata)
251
+
252
+ lock_badge = " 🔒 Env lock" if env_lock else ""
253
+ console.print(
254
+ AIPPanel(
255
+ "\n".join(lines),
256
+ title=f"AIP Account{lock_badge}",
257
+ border_style=ACCENT_STYLE,
258
+ ),
259
+ )
260
+
261
+
113
262
  def _check_account_overwrite(name: str, store: AccountStore, overwrite: bool) -> dict[str, str] | None:
114
263
  """Check if account exists and handle overwrite logic.
115
264
 
@@ -155,8 +304,8 @@ def _get_credentials_non_interactive(
155
304
  if not sys.stdin.isatty():
156
305
  return url, sys.stdin.read().strip()
157
306
  console.print(
158
- f"[{ERROR_STYLE}]Error: --key requires stdin input. "
159
- f"Use: cat key.txt | {command_name} {name} --url {url} --key[/]",
307
+ f"[{ERROR_STYLE}]Error: --key expects stdin or an explicit value. "
308
+ f"Use '--key <value>' or pipe: cat key.txt | {command_name} {name} --url {url} --key[/]",
160
309
  )
161
310
  raise click.Abort()
162
311
  # URL provided, prompt for key
@@ -179,7 +328,8 @@ def _get_credentials_interactive(read_key_from_stdin: bool, existing: dict[str,
179
328
  """
180
329
  if read_key_from_stdin:
181
330
  console.print(
182
- f"[{ERROR_STYLE}]Error: --key requires --url. For non-interactive mode, provide both: --url <url> --key[/]",
331
+ f"[{ERROR_STYLE}]Error: --key requires --url. "
332
+ f"Provide --url with --key <value|-> for non-interactive use or omit --key to be prompted.[/]",
183
333
  )
184
334
  raise click.Abort()
185
335
  # Fully interactive
@@ -237,7 +387,7 @@ def _preserve_existing_values(
237
387
 
238
388
  def _collect_credentials_from_inputs(
239
389
  url: str | None,
240
- read_key_from_stdin: bool,
390
+ api_key_input: str | None,
241
391
  name: str,
242
392
  existing: dict[str, str] | None,
243
393
  command_name: str,
@@ -247,7 +397,7 @@ def _collect_credentials_from_inputs(
247
397
 
248
398
  Args:
249
399
  url: Optional URL from flag.
250
- read_key_from_stdin: Whether to read key from stdin.
400
+ api_key_input: API key value from flag (or "-" when stdin requested).
251
401
  name: Account name (for error messages).
252
402
  existing: Existing account data.
253
403
  command_name: Command name for error messages.
@@ -256,6 +406,29 @@ def _collect_credentials_from_inputs(
256
406
  Returns:
257
407
  Tuple of (api_url, api_key).
258
408
  """
409
+ provided_key = api_key_input if api_key_input not in (None, "-") else None
410
+ read_key_from_stdin = api_key_input == "-"
411
+
412
+ if provided_key and url:
413
+ # Fully non-interactive: URL and key provided via flags
414
+ return url, provided_key
415
+
416
+ if provided_key:
417
+ # Reuse stored URL if present; otherwise require --url
418
+ if existing_url:
419
+ return existing_url, provided_key
420
+ if existing:
421
+ console.print(
422
+ f"[{ERROR_STYLE}]Error: Account '{name}' is missing an API URL. "
423
+ f"Provide --url to set it when rotating the key.[/]"
424
+ )
425
+ else:
426
+ console.print(
427
+ f"[{ERROR_STYLE}]Error: --key requires --url for new accounts. "
428
+ f"Run without --key for prompts or pass both flags for non-interactive setup.[/]",
429
+ )
430
+ raise click.Abort()
431
+
259
432
  if url and read_key_from_stdin:
260
433
  # Non-interactive: URL from flag, key from stdin
261
434
  return _get_credentials_non_interactive(url, True, name, command_name)
@@ -271,15 +444,25 @@ def _collect_credentials_from_inputs(
271
444
 
272
445
  def _collect_account_credentials(
273
446
  url: str | None,
274
- read_key_from_stdin: bool,
447
+ api_key_input: str | None,
275
448
  name: str,
276
449
  existing: dict[str, str] | None,
277
450
  ) -> tuple[str, str]:
278
451
  """Collect account credentials from various input methods.
279
452
 
453
+ Examples:
454
+ # Inline key
455
+ aip accounts add prod --url https://api.example.com --key sk-abc123
456
+
457
+ # Stdin (useful for scripts)
458
+ echo "sk-abc123" | aip accounts add prod --url https://api.example.com --key
459
+
460
+ # Fully interactive
461
+ aip accounts add prod
462
+
280
463
  Args:
281
464
  url: Optional URL from flag.
282
- read_key_from_stdin: Whether to read key from stdin.
465
+ api_key_input: API key value from flag (or "-" when stdin requested).
283
466
  name: Account name (for error messages).
284
467
  existing: Existing account data.
285
468
 
@@ -293,9 +476,7 @@ def _collect_account_credentials(
293
476
  existing_url = existing.get("api_url", "") if existing else ""
294
477
  existing_key = existing.get("api_key", "") if existing else ""
295
478
 
296
- api_url, api_key = _collect_credentials_from_inputs(
297
- url, read_key_from_stdin, name, existing, command_name, existing_url
298
- )
479
+ api_url, api_key = _collect_credentials_from_inputs(url, api_key_input, name, existing, command_name, existing_url)
299
480
 
300
481
  # Preserve stored values when blank input is provided during edit
301
482
  api_url, api_key = _preserve_existing_values(api_url, api_key, existing_url, existing_key)
@@ -311,9 +492,12 @@ def _collect_account_credentials(
311
492
  @click.option("--url", help="API URL (required for non-interactive mode)")
312
493
  @click.option(
313
494
  "--key",
314
- "read_key_from_stdin",
315
- is_flag=True,
316
- help="Read API key from stdin (secure, for scripts). Requires --url.",
495
+ "api_key_input",
496
+ type=str,
497
+ is_flag=False,
498
+ flag_value="-",
499
+ default=None,
500
+ help="API key value. Pass without a value or '-' to read from stdin. Requires --url for non-interactive use.",
317
501
  )
318
502
  @click.option(
319
503
  "--yes",
@@ -324,7 +508,7 @@ def _collect_account_credentials(
324
508
  def add_account(
325
509
  name: str,
326
510
  url: str | None,
327
- read_key_from_stdin: bool,
511
+ api_key_input: str | None,
328
512
  overwrite: bool,
329
513
  ) -> None:
330
514
  """Add a new account profile.
@@ -332,7 +516,7 @@ def add_account(
332
516
  NAME is the account name (1-32 chars, alphanumeric, dash, underscore).
333
517
 
334
518
  By default, this command runs interactively, prompting for API URL and key.
335
- For non-interactive use, both --url and --key (stdin) are required.
519
+ For non-interactive use, provide --url with --key <value> or --key - (stdin).
336
520
 
337
521
  If the account already exists, use --yes to overwrite without prompting.
338
522
  To update an existing account, use [bold]aip accounts edit <name>[/bold] instead.
@@ -343,7 +527,7 @@ def add_account(
343
527
  existing = _check_account_overwrite(name, store, overwrite)
344
528
 
345
529
  # Collect credentials
346
- api_url, api_key = _collect_account_credentials(url, read_key_from_stdin, name, existing)
530
+ api_url, api_key = _collect_account_credentials(url, api_key_input, name, existing)
347
531
 
348
532
  # Save account
349
533
  try:
@@ -363,14 +547,17 @@ def add_account(
363
547
  @click.option("--url", help="API URL (optional, leave blank to keep current)")
364
548
  @click.option(
365
549
  "--key",
366
- "read_key_from_stdin",
367
- is_flag=True,
368
- help="Read API key from stdin (secure, for scripts). Uses stored URL unless --url is provided.",
550
+ "api_key_input",
551
+ type=str,
552
+ is_flag=False,
553
+ flag_value="-",
554
+ default=None,
555
+ help="API key value. Pass without a value or '-' to read from stdin. Uses stored URL unless --url is provided.",
369
556
  )
370
557
  def edit_account(
371
558
  name: str,
372
559
  url: str | None,
373
- read_key_from_stdin: bool,
560
+ api_key_input: str | None,
374
561
  ) -> None:
375
562
  """Edit an existing account profile's URL or key.
376
563
 
@@ -379,8 +566,8 @@ def edit_account(
379
566
  By default, this command runs interactively, showing current values and
380
567
  prompting for new ones. Leave fields blank to keep current values.
381
568
 
382
- For non-interactive use, provide --url to change the URL, --key (stdin) to rotate the key,
383
- or both. Stored values are reused for any fields not provided.
569
+ For non-interactive use, provide --url to change the URL, --key <value> to rotate the key,
570
+ or --key - (stdin) for scripts. Stored values are reused for any fields not provided.
384
571
  """
385
572
  store = get_account_store()
386
573
 
@@ -392,7 +579,7 @@ def edit_account(
392
579
  raise click.Abort()
393
580
 
394
581
  # Collect credentials (will pre-fill existing values in interactive mode)
395
- api_url, api_key = _collect_account_credentials(url, read_key_from_stdin, name, existing)
582
+ api_url, api_key = _collect_account_credentials(url, api_key_input, name, existing)
396
583
 
397
584
  # Save account
398
585
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: glaip-sdk
3
- Version: 0.5.3
3
+ Version: 0.5.5
4
4
  Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
5
5
  License: MIT
6
6
  Author: Raymond Christopher
@@ -6,7 +6,7 @@ glaip_sdk/cli/account_store.py,sha256=NXuAVPaJS_32Aw1VTaZCNwIID-gADw4F_UMieoWmg3
6
6
  glaip_sdk/cli/agent_config.py,sha256=YAbFKrTNTRqNA6b0i0Q3pH-01rhHDRi5v8dxSFwGSwM,2401
7
7
  glaip_sdk/cli/auth.py,sha256=9hfjZyd4cx2_mImqykJ1sWQsuVTR2gy6D4hFqAQNKL4,24129
8
8
  glaip_sdk/cli/commands/__init__.py,sha256=6Z3ASXDut0lAbUX_umBFtxPzzFyqoiZfVeTahThFu1A,219
9
- glaip_sdk/cli/commands/accounts.py,sha256=B5itsUzqoH_hBRYOVd2m4nPoIuBbPDIoK974zKMm9NE,18635
9
+ glaip_sdk/cli/commands/accounts.py,sha256=NTexdyiv9Qp3xZMxmtwOWeCkRDHegyk06O9J3UWyXHQ,24644
10
10
  glaip_sdk/cli/commands/agents.py,sha256=WCOzllyh_Znwlju5camT4vE6OeRJbsAmjWwcyiAqWs4,48429
11
11
  glaip_sdk/cli/commands/common_config.py,sha256=IY13gPkeifXxSdpzRFUvfRin8J7s38p6Y7TYjdGw7w4,2474
12
12
  glaip_sdk/cli/commands/configure.py,sha256=95PQiJnpvsdH02v_tLVANd64qAJJnZKlhNe4tpfWIS4,30262
@@ -107,7 +107,7 @@ glaip_sdk/utils/resource_refs.py,sha256=vF34kyAtFBLnaKnQVrsr2st1JiSxVbIZ4yq0DelJ
107
107
  glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
108
108
  glaip_sdk/utils/serialization.py,sha256=z-qpvWLSBrGK3wbUclcA1UIKLXJedTnMSwPdq-FF4lo,13308
109
109
  glaip_sdk/utils/validation.py,sha256=Vt8oSnn7OM6ns5vjOl5FwGIMWBPb0yI6RD5XL_L5_4M,6826
110
- glaip_sdk-0.5.3.dist-info/METADATA,sha256=RwJBNhUEnTWDIPr-X4oYiLk-8GtG2IzBoV41jxGkad0,7053
111
- glaip_sdk-0.5.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
112
- glaip_sdk-0.5.3.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
113
- glaip_sdk-0.5.3.dist-info/RECORD,,
110
+ glaip_sdk-0.5.5.dist-info/METADATA,sha256=JN-k8loq68PUth6CcKkzfbnOYkqN5Fv6jlJhxnS4AoU,7053
111
+ glaip_sdk-0.5.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
112
+ glaip_sdk-0.5.5.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
113
+ glaip_sdk-0.5.5.dist-info/RECORD,,