qbraid-cli 0.12.0a2__tar.gz → 0.12.0a3__tar.gz
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.
- {qbraid_cli-0.12.0a2/qbraid_cli.egg-info → qbraid_cli-0.12.0a3}/PKG-INFO +1 -1
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/pyproject.toml +1 -1
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/envs/app.py +739 -93
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3/qbraid_cli.egg-info}/PKG-INFO +1 -1
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/LICENSE +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/README.md +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/_version.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/account/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/account/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/admin/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/admin/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/admin/headers.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/admin/validation.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/chat/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/chat/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/configure/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/configure/actions.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/configure/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/configure/claude_config.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/devices/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/devices/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/devices/validation.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/envs/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/envs/activate.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/envs/create.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/envs/data_handling.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/exceptions.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/files/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/files/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/handlers.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/jobs/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/jobs/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/jobs/toggle_braket.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/jobs/validation.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/kernels/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/kernels/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/main.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/mcp/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/mcp/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/mcp/serve.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/pip/__init__.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/pip/app.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/pip/hooks.py +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli/py.typed +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli.egg-info/SOURCES.txt +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli.egg-info/dependency_links.txt +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli.egg-info/entry_points.txt +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli.egg-info/requires.txt +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/qbraid_cli.egg-info/top_level.txt +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/setup.cfg +0 -0
- {qbraid_cli-0.12.0a2 → qbraid_cli-0.12.0a3}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qbraid-cli
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.0a3
|
|
4
4
|
Summary: Command Line Interface for interacting with all parts of the qBraid platform.
|
|
5
5
|
Author-email: qBraid Development Team <contact@qbraid.com>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qbraid-cli"
|
|
7
|
-
version = "0.12.
|
|
7
|
+
version = "0.12.0a3"
|
|
8
8
|
description = "Command Line Interface for interacting with all parts of the qBraid platform."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "qBraid Development Team", email = "contact@qbraid.com" }]
|
|
@@ -440,6 +440,179 @@ def envs_available():
|
|
|
440
440
|
console.print("[dim]Create custom environments with: qbraid envs create[/dim]")
|
|
441
441
|
|
|
442
442
|
|
|
443
|
+
@envs_app.command(name="info")
|
|
444
|
+
def envs_info(
|
|
445
|
+
identifier: str = typer.Argument(..., help="Environment identifier (env_id or slug)"),
|
|
446
|
+
):
|
|
447
|
+
"""
|
|
448
|
+
Get detailed information about an environment.
|
|
449
|
+
|
|
450
|
+
Accepts either a local env_id or a cloud slug. Shows information from
|
|
451
|
+
both local registry and cloud catalog when available.
|
|
452
|
+
|
|
453
|
+
Examples:
|
|
454
|
+
$ qbraid envs info qiskit_1.0 # By slug (cloud + local if installed)
|
|
455
|
+
$ qbraid envs info a1b2 # By env_id (local only)
|
|
456
|
+
$ qbraid envs info my_custom_abc123 # Custom environment
|
|
457
|
+
"""
|
|
458
|
+
from qbraid_core.services.environments import EnvironmentRegistryManager
|
|
459
|
+
|
|
460
|
+
console = Console()
|
|
461
|
+
|
|
462
|
+
# Try to find in local registry first
|
|
463
|
+
local_entry = None
|
|
464
|
+
local_entries = []
|
|
465
|
+
slug_to_query = None
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
registry = EnvironmentRegistryManager()
|
|
469
|
+
|
|
470
|
+
# First try by env_id (exact match)
|
|
471
|
+
local_entry = registry.find_by_env_id(identifier)
|
|
472
|
+
if local_entry:
|
|
473
|
+
local_entries = [local_entry]
|
|
474
|
+
slug_to_query = local_entry.slug # May be None for local-only envs
|
|
475
|
+
else:
|
|
476
|
+
# Try by slug (may return multiple installations)
|
|
477
|
+
local_entries = registry.find_all_by_slug(identifier)
|
|
478
|
+
if local_entries:
|
|
479
|
+
slug_to_query = identifier
|
|
480
|
+
else:
|
|
481
|
+
# Not found locally, try cloud with this identifier as slug
|
|
482
|
+
slug_to_query = identifier
|
|
483
|
+
except Exception:
|
|
484
|
+
# Registry not available - try cloud only
|
|
485
|
+
slug_to_query = identifier
|
|
486
|
+
|
|
487
|
+
# Try to fetch from cloud API
|
|
488
|
+
cloud_env = None
|
|
489
|
+
if slug_to_query:
|
|
490
|
+
try:
|
|
491
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
492
|
+
|
|
493
|
+
def get_cloud_info():
|
|
494
|
+
client = EnvironmentManagerClient()
|
|
495
|
+
return client.get_environment_by_slug(slug_to_query)
|
|
496
|
+
|
|
497
|
+
cloud_env = run_progress_task(
|
|
498
|
+
get_cloud_info,
|
|
499
|
+
description="Fetching environment details...",
|
|
500
|
+
error_message="Failed to fetch from cloud",
|
|
501
|
+
)
|
|
502
|
+
except Exception:
|
|
503
|
+
# Cloud lookup failed - that's OK if we have local info
|
|
504
|
+
pass
|
|
505
|
+
|
|
506
|
+
# If nothing found anywhere, exit with error
|
|
507
|
+
if not local_entries and not cloud_env:
|
|
508
|
+
console.print(f"[red]❌ Environment not found:[/red] {identifier}")
|
|
509
|
+
console.print("[dim]Not found in local registry or cloud catalog.[/dim]")
|
|
510
|
+
raise typer.Exit(1)
|
|
511
|
+
|
|
512
|
+
# --- Display Cloud Info (if available) ---
|
|
513
|
+
if cloud_env:
|
|
514
|
+
name = cloud_env.get("displayName") or cloud_env.get("name") or "N/A"
|
|
515
|
+
description = cloud_env.get("description") or "No description"
|
|
516
|
+
owner = cloud_env.get("owner") or cloud_env.get("userId") or "Unknown"
|
|
517
|
+
python_version = cloud_env.get("pythonVersion") or "Unknown"
|
|
518
|
+
visibility = cloud_env.get("visibility") or "private"
|
|
519
|
+
tags = cloud_env.get("tags") or []
|
|
520
|
+
image_tag = cloud_env.get("imageTag") or cloud_env.get("image_tag") or "N/A"
|
|
521
|
+
available = cloud_env.get("available_for_installation", False)
|
|
522
|
+
|
|
523
|
+
visibility_colors = {"public": "green", "private": "yellow", "shared": "cyan"}
|
|
524
|
+
vis_color = visibility_colors.get(visibility, "white")
|
|
525
|
+
|
|
526
|
+
console.print(f"\n📦 [bold]{name}[/bold]")
|
|
527
|
+
console.print(f" [dim]{description}[/dim]")
|
|
528
|
+
|
|
529
|
+
console.print(f"\n[bold]☁️ Cloud Info:[/bold]")
|
|
530
|
+
console.print(f" Slug: {slug_to_query}")
|
|
531
|
+
console.print(f" Owner: {owner}")
|
|
532
|
+
console.print(f" Python: {python_version}")
|
|
533
|
+
console.print(f" Visibility: [{vis_color}]{visibility}[/{vis_color}]")
|
|
534
|
+
console.print(f" Image Tag: {image_tag}")
|
|
535
|
+
|
|
536
|
+
if tags:
|
|
537
|
+
console.print(f" Tags: {', '.join(tags)}")
|
|
538
|
+
|
|
539
|
+
# Publish status
|
|
540
|
+
review_status = cloud_env.get("reviewStatus")
|
|
541
|
+
if review_status:
|
|
542
|
+
status_colors = {
|
|
543
|
+
"none": "dim",
|
|
544
|
+
"requested": "yellow",
|
|
545
|
+
"pending": "cyan",
|
|
546
|
+
"approved": "green",
|
|
547
|
+
"denied": "red",
|
|
548
|
+
}
|
|
549
|
+
color = status_colors.get(review_status, "white")
|
|
550
|
+
console.print(f" Publish Status: [{color}]{review_status}[/{color}]")
|
|
551
|
+
|
|
552
|
+
# --- Display Local Info (if available) ---
|
|
553
|
+
if local_entries:
|
|
554
|
+
# If no cloud info, show header from local entry
|
|
555
|
+
if not cloud_env:
|
|
556
|
+
entry = local_entries[0]
|
|
557
|
+
console.print(f"\n📦 [bold]{entry.name}[/bold]")
|
|
558
|
+
if entry.description:
|
|
559
|
+
console.print(f" [dim]{entry.description}[/dim]")
|
|
560
|
+
|
|
561
|
+
console.print(f"\n[bold]💻 Local Installation{'s' if len(local_entries) > 1 else ''}:[/bold]")
|
|
562
|
+
|
|
563
|
+
for entry in local_entries:
|
|
564
|
+
kernel_status = "✓ kernel" if entry.has_kernel else "○ no kernel"
|
|
565
|
+
env_type = entry.type.value if hasattr(entry.type, "value") else str(entry.type)
|
|
566
|
+
|
|
567
|
+
console.print(f"\n [bold]env_id:[/bold] {entry.env_id}")
|
|
568
|
+
console.print(f" Path: {entry.path}")
|
|
569
|
+
console.print(f" Type: {env_type}")
|
|
570
|
+
if entry.python_version:
|
|
571
|
+
console.print(f" Python: {entry.python_version}")
|
|
572
|
+
if entry.python_executable:
|
|
573
|
+
console.print(f" Executable: {entry.python_executable}")
|
|
574
|
+
if entry.platform:
|
|
575
|
+
console.print(f" Platform: {entry.platform}")
|
|
576
|
+
if entry.kernel_name:
|
|
577
|
+
console.print(f" Kernel: {entry.kernel_name} [{kernel_status}]")
|
|
578
|
+
else:
|
|
579
|
+
console.print(f" Kernel: [{kernel_status}]")
|
|
580
|
+
if entry.shell_prompt:
|
|
581
|
+
console.print(f" Shell Prompt: {entry.shell_prompt}")
|
|
582
|
+
if entry.installed_at:
|
|
583
|
+
console.print(f" Installed: {entry.installed_at}")
|
|
584
|
+
elif entry.registered_at:
|
|
585
|
+
console.print(f" Registered: {entry.registered_at}")
|
|
586
|
+
if entry.slug:
|
|
587
|
+
console.print(f" Slug: {entry.slug}")
|
|
588
|
+
elif cloud_env:
|
|
589
|
+
console.print(f"\n[bold]💻 Local Installation:[/bold]")
|
|
590
|
+
console.print(f" [dim]Not installed locally[/dim]")
|
|
591
|
+
|
|
592
|
+
# --- Show Actions ---
|
|
593
|
+
console.print(f"\n[bold]Actions:[/bold]")
|
|
594
|
+
|
|
595
|
+
if cloud_env:
|
|
596
|
+
available = cloud_env.get("available_for_installation", False)
|
|
597
|
+
visibility = cloud_env.get("visibility", "private")
|
|
598
|
+
|
|
599
|
+
if available and not local_entries:
|
|
600
|
+
console.print(f" Install: [bold]qbraid envs install {slug_to_query}[/bold]")
|
|
601
|
+
if visibility == "private":
|
|
602
|
+
console.print(f" Share: [bold]qbraid envs share {slug_to_query} <email>[/bold]")
|
|
603
|
+
console.print(f" Publish: [bold]qbraid envs publish {slug_to_query}[/bold]")
|
|
604
|
+
|
|
605
|
+
if local_entries:
|
|
606
|
+
entry = local_entries[0]
|
|
607
|
+
if not entry.slug:
|
|
608
|
+
# Local-only environment - can be uploaded
|
|
609
|
+
console.print(f" Upload: [bold]qbraid envs upload {entry.env_id}[/bold]")
|
|
610
|
+
if not entry.has_kernel:
|
|
611
|
+
console.print(f" Add kernel: [bold]qbraid kernels add {entry.env_id}[/bold]")
|
|
612
|
+
console.print(f" Activate: [bold]qbraid envs activate {entry.env_id}[/bold]")
|
|
613
|
+
console.print(f" Remove: [bold]qbraid envs remove {entry.env_id}[/bold]")
|
|
614
|
+
|
|
615
|
+
|
|
443
616
|
@envs_app.command(name="install")
|
|
444
617
|
def envs_install(
|
|
445
618
|
env_slug: str = typer.Argument(..., help="Environment slug to install (from 'qbraid envs available')"),
|
|
@@ -572,130 +745,603 @@ def envs_install(
|
|
|
572
745
|
raise typer.Exit(1)
|
|
573
746
|
|
|
574
747
|
|
|
575
|
-
@envs_app.command(name="
|
|
576
|
-
def
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
None,
|
|
580
|
-
"--path",
|
|
581
|
-
"-p",
|
|
582
|
-
help="Path to environment directory (default: lookup from registry)"
|
|
748
|
+
@envs_app.command(name="upload")
|
|
749
|
+
def envs_upload(
|
|
750
|
+
identifier: str = typer.Argument(
|
|
751
|
+
..., help="Environment name (if unique) or env_id to upload"
|
|
583
752
|
),
|
|
584
753
|
overwrite: bool = typer.Option(
|
|
585
754
|
False,
|
|
586
755
|
"--overwrite",
|
|
587
756
|
"-o",
|
|
588
|
-
help="Overwrite existing
|
|
757
|
+
help="Overwrite existing uploaded environment"
|
|
589
758
|
),
|
|
590
759
|
):
|
|
591
760
|
"""
|
|
592
|
-
|
|
761
|
+
Upload environment to qBraid cloud storage (private by default).
|
|
593
762
|
|
|
594
|
-
This
|
|
595
|
-
|
|
763
|
+
This uploads your local environment to make it available for sharing
|
|
764
|
+
with specific users. Use 'qbraid envs publish' after upload to request
|
|
765
|
+
public visibility.
|
|
596
766
|
|
|
597
767
|
Examples:
|
|
598
|
-
$ qbraid envs
|
|
599
|
-
$ qbraid envs
|
|
600
|
-
$ qbraid envs publish my_env --overwrite
|
|
768
|
+
$ qbraid envs upload my_custom_env
|
|
769
|
+
$ qbraid envs upload a1b2 --overwrite
|
|
601
770
|
"""
|
|
602
|
-
|
|
603
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
771
|
+
import asyncio
|
|
604
772
|
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
605
|
-
from qbraid_core.services.environments.registry import EnvironmentRegistryManager
|
|
606
773
|
|
|
607
774
|
console = Console()
|
|
608
775
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
env_path = path.expanduser().resolve()
|
|
612
|
-
if not env_path.exists():
|
|
613
|
-
console.print(f"[red]❌ Error:[/red] Path does not exist: {env_path}")
|
|
614
|
-
raise typer.Exit(1)
|
|
615
|
-
else:
|
|
616
|
-
# Look up from registry by slug
|
|
617
|
-
try:
|
|
618
|
-
registry_mgr = EnvironmentRegistryManager()
|
|
619
|
-
found = registry_mgr.find_by_slug(slug)
|
|
620
|
-
if not found:
|
|
621
|
-
console.print(f"[red]❌ Error:[/red] Environment with slug '{slug}' not found in registry")
|
|
622
|
-
console.print("\n[yellow]💡 Tip:[/yellow] Use --path to specify environment directory manually.")
|
|
623
|
-
raise typer.Exit(1)
|
|
624
|
-
env_id, env = found
|
|
625
|
-
env_path = Path(env.path)
|
|
626
|
-
except Exception as err:
|
|
627
|
-
console.print(f"[red]❌ Error:[/red] Failed to locate environment: {err}")
|
|
628
|
-
console.print("\n[yellow]💡 Tip:[/yellow] Use --path to specify environment directory manually.")
|
|
629
|
-
raise typer.Exit(1)
|
|
630
|
-
|
|
631
|
-
console.print(f"🚀 Publishing environment: [bold]{slug}[/bold]")
|
|
632
|
-
console.print(f"📂 Source: {env_path}\n")
|
|
633
|
-
|
|
634
|
-
# Define async function
|
|
635
|
-
async def publish_environment_async():
|
|
636
|
-
"""Async wrapper for environment publishing."""
|
|
776
|
+
async def upload_environment_async():
|
|
777
|
+
"""Async wrapper for environment upload."""
|
|
637
778
|
client = EnvironmentManagerClient()
|
|
638
779
|
|
|
639
|
-
|
|
640
|
-
stages = {"archive": False, "upload": False}
|
|
641
|
-
|
|
642
|
-
def progress_callback(stage: str, completed: int, total: int):
|
|
643
|
-
"""Track progress across stages."""
|
|
644
|
-
stages[stage] = True
|
|
645
|
-
|
|
646
|
-
with Progress(
|
|
780
|
+
progress_columns = (
|
|
647
781
|
SpinnerColumn(),
|
|
648
|
-
TextColumn("
|
|
782
|
+
TextColumn("{task.description}"),
|
|
649
783
|
BarColumn(),
|
|
650
784
|
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
651
|
-
|
|
652
|
-
)
|
|
653
|
-
# Add tasks
|
|
654
|
-
archive_task = progress.add_task("📦 Creating archive...", total=100)
|
|
655
|
-
upload_task = progress.add_task("☁️ Uploading to cloud...", total=100)
|
|
656
|
-
|
|
657
|
-
try:
|
|
658
|
-
# TODO: Update this when env_share_publish branch is merged
|
|
659
|
-
# This will use remote_publish_environment and handle directory renaming
|
|
660
|
-
raise NotImplementedError(
|
|
661
|
-
"Publish command will be updated when env_share_publish branch is merged. "
|
|
662
|
-
"It will use remote_publish_environment to get slug from API, "
|
|
663
|
-
"rename directory from env_id to slug, update paths, and upload."
|
|
664
|
-
)
|
|
665
|
-
# result = await client.publish_environment(
|
|
666
|
-
# slug=slug,
|
|
667
|
-
# env_path=str(env_path),
|
|
668
|
-
# overwrite=overwrite,
|
|
669
|
-
# progress_callback=progress_callback
|
|
670
|
-
# )
|
|
785
|
+
TimeElapsedColumn(),
|
|
786
|
+
)
|
|
671
787
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
788
|
+
with Progress(*progress_columns, console=console, transient=False) as progress:
|
|
789
|
+
tasks: dict[str, int] = {}
|
|
790
|
+
|
|
791
|
+
def get_task(stage: str, description: str, total: Optional[int]) -> int:
|
|
792
|
+
task_id = tasks.get(stage)
|
|
793
|
+
if task_id is None:
|
|
794
|
+
task_total = total if total and total > 0 else 100
|
|
795
|
+
task_id = progress.add_task(description, total=task_total)
|
|
796
|
+
tasks[stage] = task_id
|
|
797
|
+
return task_id
|
|
798
|
+
|
|
799
|
+
def progress_callback(stage: str, completed: int, total: int) -> None:
|
|
800
|
+
total_value = total if total and total > 0 else 100
|
|
801
|
+
if stage == "prepare":
|
|
802
|
+
task_id = get_task("prepare", "📋 Preparing environment...", total_value)
|
|
803
|
+
progress.update(task_id, completed=completed, total=total_value)
|
|
804
|
+
elif stage == "archive":
|
|
805
|
+
task_id = get_task("archive", "📦 Creating archive...", total_value)
|
|
806
|
+
progress.update(task_id, completed=completed, total=total_value)
|
|
807
|
+
elif stage == "upload":
|
|
808
|
+
task_id = get_task("upload", "☁️ Uploading to cloud...", total_value)
|
|
809
|
+
progress.update(task_id, completed=completed, total=total_value)
|
|
810
|
+
|
|
811
|
+
result = await client.upload_environment(
|
|
812
|
+
identifier=identifier,
|
|
813
|
+
overwrite=overwrite,
|
|
814
|
+
progress_callback=progress_callback,
|
|
815
|
+
)
|
|
677
816
|
|
|
678
|
-
|
|
817
|
+
return result
|
|
679
818
|
|
|
680
|
-
|
|
681
|
-
raise err
|
|
819
|
+
console.print(f"🚀 Uploading environment: [bold]{identifier}[/bold]\n")
|
|
682
820
|
|
|
683
|
-
# Run async function
|
|
684
|
-
import asyncio
|
|
685
821
|
try:
|
|
686
|
-
result = asyncio.run(
|
|
822
|
+
result = asyncio.run(upload_environment_async())
|
|
823
|
+
|
|
824
|
+
console.print(f"\n[green]✅ Upload completed![/green]")
|
|
825
|
+
console.print(f"📍 Slug: [bold]{result.get('slug', 'N/A')}[/bold]")
|
|
826
|
+
console.print(f"🆔 Env ID: {result.get('env_id', 'N/A')}")
|
|
827
|
+
if result.get('bucket'):
|
|
828
|
+
console.print(f"☁️ Bucket: {result.get('bucket')}")
|
|
687
829
|
|
|
688
|
-
console.print(f"\n
|
|
689
|
-
console.print(f"
|
|
690
|
-
console.print(f"
|
|
691
|
-
console.print(f"\n🎯 Users can now install with:")
|
|
692
|
-
console.print(f" [bold cyan]qbraid envs install {slug}[/bold cyan]")
|
|
830
|
+
console.print(f"\n🎯 Next steps:")
|
|
831
|
+
console.print(f" • Share privately: [bold]qbraid envs share {result.get('slug')} <email>[/bold]")
|
|
832
|
+
console.print(f" • Request public: [bold]qbraid envs publish {result.get('slug')}[/bold]")
|
|
693
833
|
|
|
694
834
|
except KeyboardInterrupt:
|
|
695
|
-
console.print("\n[yellow]⚠️
|
|
835
|
+
console.print("\n[yellow]⚠️ Upload cancelled by user[/yellow]")
|
|
836
|
+
raise typer.Exit(1)
|
|
837
|
+
except Exception as err:
|
|
838
|
+
console.print(f"\n[red]❌ Upload failed:[/red] {err}")
|
|
839
|
+
raise typer.Exit(1)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
@envs_app.command(name="publish")
|
|
843
|
+
def envs_publish(
|
|
844
|
+
slug: str = typer.Argument(..., help="Environment slug (from upload)"),
|
|
845
|
+
):
|
|
846
|
+
"""
|
|
847
|
+
Request to publish environment to the public catalog.
|
|
848
|
+
|
|
849
|
+
This submits your uploaded environment for admin review. Once approved,
|
|
850
|
+
it will be publicly visible and installable by all users.
|
|
851
|
+
|
|
852
|
+
The environment must be uploaded first with 'qbraid envs upload'.
|
|
853
|
+
|
|
854
|
+
Examples:
|
|
855
|
+
$ qbraid envs publish my_env_abc123
|
|
856
|
+
"""
|
|
857
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
858
|
+
|
|
859
|
+
console = Console()
|
|
860
|
+
|
|
861
|
+
def request_publish():
|
|
862
|
+
client = EnvironmentManagerClient()
|
|
863
|
+
return client.request_publish(slug)
|
|
864
|
+
|
|
865
|
+
console.print(f"📤 Requesting to publish: [bold]{slug}[/bold]\n")
|
|
866
|
+
|
|
867
|
+
try:
|
|
868
|
+
result = run_progress_task(
|
|
869
|
+
request_publish,
|
|
870
|
+
description="Submitting publish request...",
|
|
871
|
+
error_message="Failed to submit publish request"
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
console.print(f"\n[green]✅ Publish request submitted![/green]")
|
|
875
|
+
console.print(f"📍 Slug: [bold]{slug}[/bold]")
|
|
876
|
+
console.print(f"📊 Status: [yellow]requested[/yellow]")
|
|
877
|
+
console.print(f"\n💡 Your request will be reviewed by qBraid admins.")
|
|
878
|
+
console.print(f" Check status with: [bold]qbraid envs publish-status {slug}[/bold]")
|
|
879
|
+
|
|
880
|
+
except Exception as err:
|
|
881
|
+
error_msg = str(err)
|
|
882
|
+
if "already" in error_msg.lower():
|
|
883
|
+
console.print(f"[yellow]⚠️ {error_msg}[/yellow]")
|
|
884
|
+
else:
|
|
885
|
+
console.print(f"[red]❌ Failed to request publish:[/red] {err}")
|
|
696
886
|
raise typer.Exit(1)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
@envs_app.command(name="publish-status")
|
|
890
|
+
def envs_publish_status(
|
|
891
|
+
slug: str = typer.Argument(..., help="Environment slug to check"),
|
|
892
|
+
):
|
|
893
|
+
"""
|
|
894
|
+
Check the publishing status of an environment.
|
|
895
|
+
|
|
896
|
+
Status values:
|
|
897
|
+
- none: Not submitted for publishing
|
|
898
|
+
- requested: Waiting for admin review
|
|
899
|
+
- pending: Under admin review
|
|
900
|
+
- approved: Published to public catalog
|
|
901
|
+
- denied: Publish request was denied
|
|
902
|
+
|
|
903
|
+
Examples:
|
|
904
|
+
$ qbraid envs publish-status my_env_abc123
|
|
905
|
+
"""
|
|
906
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
907
|
+
|
|
908
|
+
console = Console()
|
|
909
|
+
|
|
910
|
+
def get_status():
|
|
911
|
+
client = EnvironmentManagerClient()
|
|
912
|
+
return client.get_publish_status(slug)
|
|
913
|
+
|
|
914
|
+
try:
|
|
915
|
+
status = run_progress_task(
|
|
916
|
+
get_status,
|
|
917
|
+
description="Checking publish status...",
|
|
918
|
+
error_message="Failed to get publish status"
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
# Color-code status
|
|
922
|
+
status_colors = {
|
|
923
|
+
"none": "dim",
|
|
924
|
+
"requested": "yellow",
|
|
925
|
+
"pending": "cyan",
|
|
926
|
+
"approved": "green",
|
|
927
|
+
"denied": "red",
|
|
928
|
+
}
|
|
929
|
+
color = status_colors.get(status, "white")
|
|
930
|
+
|
|
931
|
+
console.print(f"\n📊 Publish Status for [bold]{slug}[/bold]")
|
|
932
|
+
console.print(f" Status: [{color}]{status}[/{color}]")
|
|
933
|
+
|
|
934
|
+
# Show helpful next steps
|
|
935
|
+
if status == "none":
|
|
936
|
+
console.print(f"\n💡 To request publishing: [bold]qbraid envs publish {slug}[/bold]")
|
|
937
|
+
elif status == "requested":
|
|
938
|
+
console.print(f"\n⏳ Waiting for admin review...")
|
|
939
|
+
console.print(f" To cancel: [bold]qbraid envs publish-cancel {slug}[/bold]")
|
|
940
|
+
elif status == "pending":
|
|
941
|
+
console.print(f"\n🔍 Currently under admin review...")
|
|
942
|
+
elif status == "approved":
|
|
943
|
+
console.print(f"\n🎉 Environment is publicly available!")
|
|
944
|
+
console.print(f" Install with: [bold]qbraid envs install {slug}[/bold]")
|
|
945
|
+
elif status == "denied":
|
|
946
|
+
console.print(f"\n❌ Publish request was denied.")
|
|
947
|
+
console.print(f" To resubmit: [bold]qbraid envs publish {slug}[/bold]")
|
|
948
|
+
|
|
697
949
|
except Exception as err:
|
|
698
|
-
console.print(f"
|
|
950
|
+
console.print(f"[red]❌ Failed to get status:[/red] {err}")
|
|
951
|
+
raise typer.Exit(1)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
@envs_app.command(name="publish-cancel")
|
|
955
|
+
def envs_publish_cancel(
|
|
956
|
+
slug: str = typer.Argument(..., help="Environment slug"),
|
|
957
|
+
):
|
|
958
|
+
"""
|
|
959
|
+
Cancel a pending publish request.
|
|
960
|
+
|
|
961
|
+
This withdraws your publish request before it's approved.
|
|
962
|
+
You can resubmit later with 'qbraid envs publish'.
|
|
963
|
+
|
|
964
|
+
Examples:
|
|
965
|
+
$ qbraid envs publish-cancel my_env_abc123
|
|
966
|
+
"""
|
|
967
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
968
|
+
|
|
969
|
+
console = Console()
|
|
970
|
+
|
|
971
|
+
def revoke_request():
|
|
972
|
+
client = EnvironmentManagerClient()
|
|
973
|
+
return client.revoke_publish_request(slug)
|
|
974
|
+
|
|
975
|
+
console.print(f"🚫 Cancelling publish request for: [bold]{slug}[/bold]\n")
|
|
976
|
+
|
|
977
|
+
try:
|
|
978
|
+
result = run_progress_task(
|
|
979
|
+
revoke_request,
|
|
980
|
+
description="Cancelling publish request...",
|
|
981
|
+
error_message="Failed to cancel publish request"
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
console.print(f"[green]✅ Publish request cancelled.[/green]")
|
|
985
|
+
console.print(f" Environment remains private/shared only.")
|
|
986
|
+
console.print(f"\n💡 To resubmit: [bold]qbraid envs publish {slug}[/bold]")
|
|
987
|
+
|
|
988
|
+
except Exception as err:
|
|
989
|
+
console.print(f"[red]❌ Failed to cancel:[/red] {err}")
|
|
990
|
+
raise typer.Exit(1)
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
@envs_app.command(name="publish-review")
|
|
994
|
+
def envs_publish_review(
|
|
995
|
+
slug: str = typer.Argument(..., help="Environment slug to review"),
|
|
996
|
+
):
|
|
997
|
+
"""
|
|
998
|
+
(Admin) Mark an environment as under review.
|
|
999
|
+
|
|
1000
|
+
This transitions the environment from 'requested' to 'pending' status,
|
|
1001
|
+
indicating an admin is actively reviewing it.
|
|
1002
|
+
|
|
1003
|
+
Examples:
|
|
1004
|
+
$ qbraid envs publish-review my_env_abc123
|
|
1005
|
+
"""
|
|
1006
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
1007
|
+
|
|
1008
|
+
console = Console()
|
|
1009
|
+
|
|
1010
|
+
def set_pending():
|
|
1011
|
+
client = EnvironmentManagerClient()
|
|
1012
|
+
return client.set_publish_pending(slug)
|
|
1013
|
+
|
|
1014
|
+
console.print(f"🔍 Marking as under review: [bold]{slug}[/bold]\n")
|
|
1015
|
+
|
|
1016
|
+
try:
|
|
1017
|
+
result = run_progress_task(
|
|
1018
|
+
set_pending,
|
|
1019
|
+
description="Updating status to pending...",
|
|
1020
|
+
error_message="Failed to update status"
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
console.print(f"[green]✅ Environment marked as under review.[/green]")
|
|
1024
|
+
console.print(f" Status: [cyan]pending[/cyan]")
|
|
1025
|
+
console.print(f"\n💡 Next steps:")
|
|
1026
|
+
console.print(f" • Approve: [bold]qbraid envs publish-approve {slug}[/bold]")
|
|
1027
|
+
console.print(f" • Deny: [bold]qbraid envs publish-deny {slug} --message \"reason\"[/bold]")
|
|
1028
|
+
|
|
1029
|
+
except Exception as err:
|
|
1030
|
+
error_msg = str(err)
|
|
1031
|
+
if "permission" in error_msg.lower() or "admin" in error_msg.lower():
|
|
1032
|
+
console.print(f"[red]❌ Admin access required.[/red]")
|
|
1033
|
+
else:
|
|
1034
|
+
console.print(f"[red]❌ Failed:[/red] {err}")
|
|
1035
|
+
raise typer.Exit(1)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
@envs_app.command(name="publish-approve")
|
|
1039
|
+
def envs_publish_approve(
|
|
1040
|
+
slug: str = typer.Argument(..., help="Environment slug to approve"),
|
|
1041
|
+
):
|
|
1042
|
+
"""
|
|
1043
|
+
(Admin) Approve and publish an environment to the public catalog.
|
|
1044
|
+
|
|
1045
|
+
This makes the environment publicly visible and installable by all users.
|
|
1046
|
+
|
|
1047
|
+
Examples:
|
|
1048
|
+
$ qbraid envs publish-approve my_env_abc123
|
|
1049
|
+
"""
|
|
1050
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
1051
|
+
|
|
1052
|
+
console = Console()
|
|
1053
|
+
|
|
1054
|
+
def approve():
|
|
1055
|
+
client = EnvironmentManagerClient()
|
|
1056
|
+
return client.approve_publish(slug)
|
|
1057
|
+
|
|
1058
|
+
console.print(f"✅ Approving environment: [bold]{slug}[/bold]\n")
|
|
1059
|
+
|
|
1060
|
+
try:
|
|
1061
|
+
result = run_progress_task(
|
|
1062
|
+
approve,
|
|
1063
|
+
description="Approving and publishing...",
|
|
1064
|
+
error_message="Failed to approve"
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
console.print(f"[green]✅ Environment published successfully![/green]")
|
|
1068
|
+
console.print(f" Slug: [bold]{slug}[/bold]")
|
|
1069
|
+
console.print(f" Visibility: [green]public[/green]")
|
|
1070
|
+
console.print(f"\n🎉 Users can now install with:")
|
|
1071
|
+
console.print(f" [bold]qbraid envs install {slug}[/bold]")
|
|
1072
|
+
|
|
1073
|
+
except Exception as err:
|
|
1074
|
+
error_msg = str(err)
|
|
1075
|
+
if "permission" in error_msg.lower() or "admin" in error_msg.lower():
|
|
1076
|
+
console.print(f"[red]❌ Admin access required.[/red]")
|
|
1077
|
+
else:
|
|
1078
|
+
console.print(f"[red]❌ Failed:[/red] {err}")
|
|
1079
|
+
raise typer.Exit(1)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
@envs_app.command(name="publish-deny")
|
|
1083
|
+
def envs_publish_deny(
|
|
1084
|
+
slug: str = typer.Argument(..., help="Environment slug to deny"),
|
|
1085
|
+
message: Optional[str] = typer.Option(
|
|
1086
|
+
None,
|
|
1087
|
+
"--message",
|
|
1088
|
+
"-m",
|
|
1089
|
+
help="Reason for denial (sent to owner)"
|
|
1090
|
+
),
|
|
1091
|
+
):
|
|
1092
|
+
"""
|
|
1093
|
+
(Admin) Deny a publish request.
|
|
1094
|
+
|
|
1095
|
+
The owner will be notified with the denial reason.
|
|
1096
|
+
|
|
1097
|
+
Examples:
|
|
1098
|
+
$ qbraid envs publish-deny my_env_abc123
|
|
1099
|
+
$ qbraid envs publish-deny my_env_abc123 --message "Missing documentation"
|
|
1100
|
+
"""
|
|
1101
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
1102
|
+
|
|
1103
|
+
console = Console()
|
|
1104
|
+
|
|
1105
|
+
def deny():
|
|
1106
|
+
client = EnvironmentManagerClient()
|
|
1107
|
+
return client.deny_publish(slug, message=message)
|
|
1108
|
+
|
|
1109
|
+
console.print(f"❌ Denying publish request: [bold]{slug}[/bold]")
|
|
1110
|
+
if message:
|
|
1111
|
+
console.print(f" Reason: {message}")
|
|
1112
|
+
console.print("")
|
|
1113
|
+
|
|
1114
|
+
try:
|
|
1115
|
+
result = run_progress_task(
|
|
1116
|
+
deny,
|
|
1117
|
+
description="Denying publish request...",
|
|
1118
|
+
error_message="Failed to deny"
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
console.print(f"[yellow]✅ Publish request denied.[/yellow]")
|
|
1122
|
+
console.print(f" Owner has been notified.")
|
|
1123
|
+
|
|
1124
|
+
except Exception as err:
|
|
1125
|
+
error_msg = str(err)
|
|
1126
|
+
if "permission" in error_msg.lower() or "admin" in error_msg.lower():
|
|
1127
|
+
console.print(f"[red]❌ Admin access required.[/red]")
|
|
1128
|
+
else:
|
|
1129
|
+
console.print(f"[red]❌ Failed:[/red] {err}")
|
|
1130
|
+
raise typer.Exit(1)
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
@envs_app.command(name="share")
|
|
1134
|
+
def envs_share(
|
|
1135
|
+
slug: str = typer.Argument(..., help="Environment slug to share"),
|
|
1136
|
+
email: str = typer.Argument(..., help="Email of user to share with"),
|
|
1137
|
+
read: bool = typer.Option(
|
|
1138
|
+
True,
|
|
1139
|
+
"--read/--no-read",
|
|
1140
|
+
help="Grant read permission (view environment)"
|
|
1141
|
+
),
|
|
1142
|
+
write: bool = typer.Option(
|
|
1143
|
+
False,
|
|
1144
|
+
"--write",
|
|
1145
|
+
help="Grant write permission (modify environment)"
|
|
1146
|
+
),
|
|
1147
|
+
execute: bool = typer.Option(
|
|
1148
|
+
True,
|
|
1149
|
+
"--execute/--no-execute",
|
|
1150
|
+
help="Grant execute permission (install environment)"
|
|
1151
|
+
),
|
|
1152
|
+
):
|
|
1153
|
+
"""
|
|
1154
|
+
Share an environment with another user.
|
|
1155
|
+
|
|
1156
|
+
By default, grants read and execute permissions (user can view and install).
|
|
1157
|
+
Use --write to also allow modifications.
|
|
1158
|
+
|
|
1159
|
+
The environment must be uploaded first with 'qbraid envs upload'.
|
|
1160
|
+
|
|
1161
|
+
Examples:
|
|
1162
|
+
$ qbraid envs share my_env_abc123 colleague@example.com
|
|
1163
|
+
$ qbraid envs share my_env_abc123 collaborator@example.com --write
|
|
1164
|
+
$ qbraid envs share my_env_abc123 viewer@example.com --no-execute
|
|
1165
|
+
"""
|
|
1166
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
1167
|
+
|
|
1168
|
+
console = Console()
|
|
1169
|
+
|
|
1170
|
+
# Build permissions list
|
|
1171
|
+
permissions = []
|
|
1172
|
+
if read:
|
|
1173
|
+
permissions.append("read")
|
|
1174
|
+
if write:
|
|
1175
|
+
permissions.append("write")
|
|
1176
|
+
if execute:
|
|
1177
|
+
permissions.append("execute")
|
|
1178
|
+
|
|
1179
|
+
if not permissions:
|
|
1180
|
+
console.print("[red]❌ Error:[/red] At least one permission must be granted.")
|
|
1181
|
+
raise typer.Exit(1)
|
|
1182
|
+
|
|
1183
|
+
def share():
|
|
1184
|
+
client = EnvironmentManagerClient()
|
|
1185
|
+
return client.share_environment(
|
|
1186
|
+
slug=slug,
|
|
1187
|
+
target_type="user",
|
|
1188
|
+
email=email,
|
|
1189
|
+
permissions=permissions
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
console.print(f"🔗 Sharing environment: [bold]{slug}[/bold]")
|
|
1193
|
+
console.print(f" With: {email}")
|
|
1194
|
+
console.print(f" Permissions: {', '.join(permissions)}\n")
|
|
1195
|
+
|
|
1196
|
+
try:
|
|
1197
|
+
result = run_progress_task(
|
|
1198
|
+
share,
|
|
1199
|
+
description="Sharing environment...",
|
|
1200
|
+
error_message="Failed to share environment"
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
console.print(f"[green]✅ Environment shared successfully![/green]")
|
|
1204
|
+
console.print(f" {email} can now access [bold]{slug}[/bold]")
|
|
1205
|
+
|
|
1206
|
+
if execute:
|
|
1207
|
+
console.print(f"\n💡 They can install with: [bold]qbraid envs install {slug}[/bold]")
|
|
1208
|
+
|
|
1209
|
+
except Exception as err:
|
|
1210
|
+
error_msg = str(err)
|
|
1211
|
+
if "not found" in error_msg.lower():
|
|
1212
|
+
console.print(f"[red]❌ Environment not found.[/red] Make sure it's uploaded first.")
|
|
1213
|
+
elif "owner" in error_msg.lower():
|
|
1214
|
+
console.print(f"[red]❌ Only the owner can share this environment.[/red]")
|
|
1215
|
+
else:
|
|
1216
|
+
console.print(f"[red]❌ Failed to share:[/red] {err}")
|
|
1217
|
+
raise typer.Exit(1)
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
@envs_app.command(name="unshare")
|
|
1221
|
+
def envs_unshare(
|
|
1222
|
+
slug: str = typer.Argument(..., help="Environment slug"),
|
|
1223
|
+
email: str = typer.Argument(..., help="Email of user to revoke access from"),
|
|
1224
|
+
):
|
|
1225
|
+
"""
|
|
1226
|
+
Revoke access to a shared environment.
|
|
1227
|
+
|
|
1228
|
+
This removes all permissions for the specified user.
|
|
1229
|
+
|
|
1230
|
+
Examples:
|
|
1231
|
+
$ qbraid envs unshare my_env_abc123 former_colleague@example.com
|
|
1232
|
+
"""
|
|
1233
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
1234
|
+
|
|
1235
|
+
console = Console()
|
|
1236
|
+
|
|
1237
|
+
def revoke():
|
|
1238
|
+
client = EnvironmentManagerClient()
|
|
1239
|
+
# Revoke all permissions
|
|
1240
|
+
return client.revoke_environment_access(
|
|
1241
|
+
slug=slug,
|
|
1242
|
+
target_type="user",
|
|
1243
|
+
email=email,
|
|
1244
|
+
permissions=["read", "write", "execute"]
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
console.print(f"🚫 Revoking access to: [bold]{slug}[/bold]")
|
|
1248
|
+
console.print(f" From: {email}\n")
|
|
1249
|
+
|
|
1250
|
+
try:
|
|
1251
|
+
result = run_progress_task(
|
|
1252
|
+
revoke,
|
|
1253
|
+
description="Revoking access...",
|
|
1254
|
+
error_message="Failed to revoke access"
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
console.print(f"[green]✅ Access revoked.[/green]")
|
|
1258
|
+
console.print(f" {email} no longer has access to [bold]{slug}[/bold]")
|
|
1259
|
+
|
|
1260
|
+
except Exception as err:
|
|
1261
|
+
error_msg = str(err)
|
|
1262
|
+
if "not found" in error_msg.lower():
|
|
1263
|
+
console.print(f"[red]❌ Environment not found.[/red]")
|
|
1264
|
+
elif "owner" in error_msg.lower():
|
|
1265
|
+
console.print(f"[red]❌ Only the owner can modify sharing settings.[/red]")
|
|
1266
|
+
else:
|
|
1267
|
+
console.print(f"[red]❌ Failed to revoke:[/red] {err}")
|
|
1268
|
+
raise typer.Exit(1)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
@envs_app.command(name="shared")
|
|
1272
|
+
def envs_shared(
|
|
1273
|
+
slug: str = typer.Argument(..., help="Environment slug to check"),
|
|
1274
|
+
):
|
|
1275
|
+
"""
|
|
1276
|
+
List users who have access to an environment.
|
|
1277
|
+
|
|
1278
|
+
Shows all users with read or write access to the specified environment.
|
|
1279
|
+
|
|
1280
|
+
Examples:
|
|
1281
|
+
$ qbraid envs shared my_env_abc123
|
|
1282
|
+
"""
|
|
1283
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
1284
|
+
from rich.table import Table
|
|
1285
|
+
|
|
1286
|
+
console = Console()
|
|
1287
|
+
|
|
1288
|
+
def get_shared():
|
|
1289
|
+
client = EnvironmentManagerClient()
|
|
1290
|
+
return client.get_environment_shared_with(slug)
|
|
1291
|
+
|
|
1292
|
+
try:
|
|
1293
|
+
result = run_progress_task(
|
|
1294
|
+
get_shared,
|
|
1295
|
+
description="Fetching shared users...",
|
|
1296
|
+
error_message="Failed to get shared users"
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
read_users = result.get("readAccessUsers", [])
|
|
1300
|
+
write_users = result.get("writeAccessUsers", [])
|
|
1301
|
+
|
|
1302
|
+
console.print(f"\n📋 Sharing info for [bold]{slug}[/bold]\n")
|
|
1303
|
+
|
|
1304
|
+
if not read_users and not write_users:
|
|
1305
|
+
console.print("[dim]This environment is not shared with anyone.[/dim]")
|
|
1306
|
+
console.print(f"\n💡 Share with: [bold]qbraid envs share {slug} <email>[/bold]")
|
|
1307
|
+
return
|
|
1308
|
+
|
|
1309
|
+
# Create table
|
|
1310
|
+
table = Table(show_header=True, header_style="bold")
|
|
1311
|
+
table.add_column("Email", style="cyan")
|
|
1312
|
+
table.add_column("Name", style="dim")
|
|
1313
|
+
table.add_column("Permissions", style="green")
|
|
1314
|
+
|
|
1315
|
+
# Track users we've already added (to avoid duplicates)
|
|
1316
|
+
seen_emails = set()
|
|
1317
|
+
|
|
1318
|
+
# Add write users first (they have more permissions)
|
|
1319
|
+
for user in write_users:
|
|
1320
|
+
email = user.get("email", "N/A")
|
|
1321
|
+
name = user.get("name", "")
|
|
1322
|
+
if email not in seen_emails:
|
|
1323
|
+
table.add_row(email, name, "read, write, execute")
|
|
1324
|
+
seen_emails.add(email)
|
|
1325
|
+
|
|
1326
|
+
# Add read-only users
|
|
1327
|
+
for user in read_users:
|
|
1328
|
+
email = user.get("email", "N/A")
|
|
1329
|
+
name = user.get("name", "")
|
|
1330
|
+
if email not in seen_emails:
|
|
1331
|
+
table.add_row(email, name, "read, execute")
|
|
1332
|
+
seen_emails.add(email)
|
|
1333
|
+
|
|
1334
|
+
console.print(table)
|
|
1335
|
+
console.print(f"\n[dim]Total: {len(seen_emails)} user(s)[/dim]")
|
|
1336
|
+
|
|
1337
|
+
except Exception as err:
|
|
1338
|
+
error_msg = str(err)
|
|
1339
|
+
if "not found" in error_msg.lower():
|
|
1340
|
+
console.print(f"[red]❌ Environment not found.[/red]")
|
|
1341
|
+
elif "owner" in error_msg.lower():
|
|
1342
|
+
console.print(f"[red]❌ Only the owner can view sharing settings.[/red]")
|
|
1343
|
+
else:
|
|
1344
|
+
console.print(f"[red]❌ Failed to get shared users:[/red] {err}")
|
|
699
1345
|
raise typer.Exit(1)
|
|
700
1346
|
|
|
701
1347
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qbraid-cli
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.0a3
|
|
4
4
|
Summary: Command Line Interface for interacting with all parts of the qBraid platform.
|
|
5
5
|
Author-email: qBraid Development Team <contact@qbraid.com>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|