From c4fa48b59435ec956fa8f8ebfe2b9814a7392133 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 15 Jan 2026 19:53:26 -0800 Subject: [PATCH 1/6] add team id for image builds --- .../prime/src/prime_cli/commands/images.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index 4916bd62..155327dd 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -36,6 +36,12 @@ def push_image( click_type=click.Choice(["linux/amd64", "linux/arm64"]), help="Target platform (defaults to linux/amd64 for Kubernetes compatibility)", ), + team_id: str = typer.Option( + None, + "--team-id", + "-t", + help="Team ID to associate the image with (for team billing and limits)", + ), ): """ Build and push a Docker image to Prime Intellect registry. @@ -44,6 +50,7 @@ def push_image( prime images push myapp:v1.0.0 prime images push myapp:latest --dockerfile custom.Dockerfile prime images push myapp:v1 --platform linux/arm64 + prime images push myapp:v1 --team-id my-team-id """ try: # Parse image reference @@ -83,15 +90,19 @@ def push_image( # Initialize build console.print("[cyan]Initiating build...[/cyan]") try: + build_payload = { + "image_name": image_name, + "image_tag": image_tag, + "dockerfile_path": dockerfile, + "platform": platform, + } + if team_id: + build_payload["team_id"] = team_id + build_response = client.request( "POST", "/images/build", - json={ - "image_name": image_name, - "image_tag": image_tag, - "dockerfile_path": dockerfile, - "platform": platform, - }, + json=build_payload, ) except UnauthorizedError: console.print( From 9d284b3ed6b62c150f874eaebf3139a9730b0b75 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 15 Jan 2026 20:07:05 -0800 Subject: [PATCH 2/6] add team id to delete --- .../prime/src/prime_cli/commands/images.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index 155327dd..fa92223b 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -283,16 +283,20 @@ def delete_image( ..., help="Image reference to delete (e.g., 'myapp:v1.0.0')" ), yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"), + team_id: str = typer.Option( + None, + "--team-id", + "-t", + help="Team ID if deleting a team image", + ), ): """ Delete an image from your registry. - Note: This removes the database record but does not delete the actual - image from Google Artifact Registry. - Examples: prime images delete myapp:v1.0.0 prime images delete myapp:latest --yes + prime images delete myapp:v1 --team-id my-team-id """ try: # Parse image reference @@ -304,16 +308,22 @@ def delete_image( image_name, image_tag = image_reference.rsplit(":", 1) + context = f" (team: {team_id})" if team_id else "" if not yes: - confirm = typer.confirm(f"Are you sure you want to delete {image_name}:{image_tag}?") + msg = f"Are you sure you want to delete {image_name}:{image_tag}{context}?" + confirm = typer.confirm(msg) if not confirm: console.print("[yellow]Cancelled[/yellow]") raise typer.Exit(0) client = APIClient() - client.request("DELETE", f"/images/{image_name}/{image_tag}") - console.print(f"[green]✓[/green] Deleted {image_name}:{image_tag}") + params = {} + if team_id: + params["teamId"] = team_id + + client.request("DELETE", f"/images/{image_name}/{image_tag}", params=params) + console.print(f"[green]✓[/green] Deleted {image_name}:{image_tag}{context}") except UnauthorizedError: console.print("[red]Error: Not authenticated. Please run 'prime login' first.[/red]") @@ -321,6 +331,8 @@ def delete_image( except APIError as e: if "404" in str(e): console.print(f"[red]Error: Image {image_reference} not found[/red]") + elif "403" in str(e): + console.print("[red]Error: You don't have permission to delete this image[/red]") else: console.print(f"[red]Error: {e}[/red]") raise typer.Exit(1) From 21db486b55396457a6a02ebf2950c4f71b1d85ba Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 15 Jan 2026 20:24:04 -0800 Subject: [PATCH 3/6] rm --team-id --- .../prime/src/prime_cli/commands/images.py | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index fa92223b..db4c21dc 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -36,12 +36,6 @@ def push_image( click_type=click.Choice(["linux/amd64", "linux/arm64"]), help="Target platform (defaults to linux/amd64 for Kubernetes compatibility)", ), - team_id: str = typer.Option( - None, - "--team-id", - "-t", - help="Team ID to associate the image with (for team billing and limits)", - ), ): """ Build and push a Docker image to Prime Intellect registry. @@ -50,7 +44,6 @@ def push_image( prime images push myapp:v1.0.0 prime images push myapp:latest --dockerfile custom.Dockerfile prime images push myapp:v1 --platform linux/arm64 - prime images push myapp:v1 --team-id my-team-id """ try: # Parse image reference @@ -63,6 +56,8 @@ def push_image( console.print( f"[bold blue]Building and pushing image:[/bold blue] {image_name}:{image_tag}" ) + if config.team_id: + console.print(f"[dim]Team: {config.team_id}[/dim]") console.print() # Initialize API client @@ -96,8 +91,8 @@ def push_image( "dockerfile_path": dockerfile, "platform": platform, } - if team_id: - build_payload["team_id"] = team_id + if config.team_id: + build_payload["team_id"] = config.team_id build_response = client.request( "POST", @@ -283,12 +278,6 @@ def delete_image( ..., help="Image reference to delete (e.g., 'myapp:v1.0.0')" ), yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"), - team_id: str = typer.Option( - None, - "--team-id", - "-t", - help="Team ID if deleting a team image", - ), ): """ Delete an image from your registry. @@ -296,7 +285,6 @@ def delete_image( Examples: prime images delete myapp:v1.0.0 prime images delete myapp:latest --yes - prime images delete myapp:v1 --team-id my-team-id """ try: # Parse image reference @@ -308,7 +296,7 @@ def delete_image( image_name, image_tag = image_reference.rsplit(":", 1) - context = f" (team: {team_id})" if team_id else "" + context = f" (team: {config.team_id})" if config.team_id else "" if not yes: msg = f"Are you sure you want to delete {image_name}:{image_tag}{context}?" confirm = typer.confirm(msg) @@ -319,8 +307,8 @@ def delete_image( client = APIClient() params = {} - if team_id: - params["teamId"] = team_id + if config.team_id: + params["teamId"] = config.team_id client.request("DELETE", f"/images/{image_name}/{image_tag}", params=params) console.print(f"[green]✓[/green] Deleted {image_name}:{image_tag}{context}") From 15484a720708e3497d1dea1bdd906471f1ea309c Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 22 Jan 2026 17:41:39 -0800 Subject: [PATCH 4/6] prime image list for team id --- .../prime/src/prime_cli/commands/images.py | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index db4c21dc..20d771bd 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -15,9 +15,7 @@ from ..utils import validate_output_format -app = typer.Typer( - help="Manage Docker images in Prime Intellect registry [closed beta]", no_args_is_help=True -) +app = typer.Typer(help="Manage Docker images in Prime Intellect registry", no_args_is_help=True) console = Console() config = Config() @@ -188,19 +186,31 @@ def push_image( @app.command("list") def list_images( output: str = typer.Option("table", "--output", "-o", help="Output format (table or json)"), + all_images: bool = typer.Option( + False, "--all", "-a", help="Show all accessible images (personal + team)" + ), ): """ List all images you've pushed to Prime Intellect registry. + By default, shows images in the current context (personal or team). + Use --all to show all accessible images including team images. + Examples: prime images list + prime images list --all prime images list --output json """ validate_output_format(output, console) try: client = APIClient() - response = client.request("GET", "/images") + # Build query params + params = {} + if config.team_id and not all_images: + params["teamId"] = config.team_id + + response = client.request("GET", "/images", params=params if params else None) images = response.get("data", []) if not images: @@ -213,8 +223,15 @@ def list_images( return # Table output - table = Table(title="Your Docker Images") + title = "Your Docker Images" + if config.team_id and not all_images: + title = f"Team Docker Images (team: {config.team_id})" + elif all_images: + title = "All Accessible Docker Images" + + table = Table(title=title) table.add_column("Image Reference", style="cyan") + table.add_column("Owner", justify="center") table.add_column("Status", justify="center") table.add_column("Size", justify="right") table.add_column("Created", style="dim") @@ -235,6 +252,13 @@ def list_images( else: status_display = f"[dim]{status}[/dim]" + # Owner type + owner_type = img.get("ownerType", "personal") + if owner_type == "team": + owner_display = "[blue]Team[/blue]" + else: + owner_display = "[dim]Personal[/dim]" + # Size size_mb = "" if img.get("sizeBytes"): @@ -250,13 +274,14 @@ def list_images( except Exception: date_str = img.get("pushedAt") or img.get("createdAt", "") - # Image reference + # Image reference - prefer displayRef for user-friendly format image_ref = ( - img.get("fullImagePath") + img.get("displayRef") + or img.get("fullImagePath") or f"{img.get('imageName', 'unknown')}:{img.get('imageTag', 'latest')}" ) - table.add_row(image_ref, status_display, size_mb, date_str) + table.add_row(image_ref, owner_display, status_display, size_mb, date_str) console.print() console.print(table) @@ -275,16 +300,21 @@ def list_images( @app.command("delete") def delete_image( image_reference: str = typer.Argument( - ..., help="Image reference to delete (e.g., 'myapp:v1.0.0')" + ..., + help="Image reference to delete (e.g., 'myapp:v1.0.0' or 'team-{teamId}/myapp:v1.0.0')", ), yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"), ): """ Delete an image from your registry. + For team images, you can use the team-prefixed format directly. + Only the image creator or team admins can delete team images. + Examples: prime images delete myapp:v1.0.0 prime images delete myapp:latest --yes + prime images delete team-abc123/myapp:v1.0.0 """ try: # Parse image reference @@ -294,9 +324,18 @@ def delete_image( ) raise typer.Exit(1) + # Check for team-prefixed format: team-{teamId}/imagename:tag + team_id = config.team_id + if "/" in image_reference: + namespace, rest = image_reference.split("/", 1) + if namespace.startswith("team-"): + # Extract team ID from the reference + team_id = namespace[5:] # Remove "team-" prefix + image_reference = rest + image_name, image_tag = image_reference.rsplit(":", 1) - context = f" (team: {config.team_id})" if config.team_id else "" + context = f" (team: {team_id})" if team_id else "" if not yes: msg = f"Are you sure you want to delete {image_name}:{image_tag}{context}?" confirm = typer.confirm(msg) @@ -307,8 +346,8 @@ def delete_image( client = APIClient() params = {} - if config.team_id: - params["teamId"] = config.team_id + if team_id: + params["teamId"] = team_id client.request("DELETE", f"/images/{image_name}/{image_tag}", params=params) console.print(f"[green]✓[/green] Deleted {image_name}:{image_tag}{context}") @@ -320,7 +359,10 @@ def delete_image( if "404" in str(e): console.print(f"[red]Error: Image {image_reference} not found[/red]") elif "403" in str(e): - console.print("[red]Error: You don't have permission to delete this image[/red]") + console.print( + "[red]Error: You don't have permission to delete this image. " + "Only the image creator or team admins can delete team images.[/red]" + ) else: console.print(f"[red]Error: {e}[/red]") raise typer.Exit(1) From d2775a30c8781ed1b2fae6312f59d59cfaaf6e3f Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 22 Jan 2026 17:52:02 -0800 Subject: [PATCH 5/6] bug bot --- .../prime/src/prime_cli/commands/images.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index 20d771bd..1565ebc3 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -316,14 +316,10 @@ def delete_image( prime images delete myapp:latest --yes prime images delete team-abc123/myapp:v1.0.0 """ - try: - # Parse image reference - if ":" not in image_reference: - console.print( - "[red]Error: Image reference must include a tag (e.g., myapp:latest)[/red]" - ) - raise typer.Exit(1) + # Store original input for error messages + original_reference = image_reference + try: # Check for team-prefixed format: team-{teamId}/imagename:tag team_id = config.team_id if "/" in image_reference: @@ -333,6 +329,13 @@ def delete_image( team_id = namespace[5:] # Remove "team-" prefix image_reference = rest + # Validate image reference has a tag (after team-prefix parsing) + if ":" not in image_reference: + console.print( + "[red]Error: Image reference must include a tag (e.g., myapp:latest)[/red]" + ) + raise typer.Exit(1) + image_name, image_tag = image_reference.rsplit(":", 1) context = f" (team: {team_id})" if team_id else "" @@ -345,9 +348,7 @@ def delete_image( client = APIClient() - params = {} - if team_id: - params["teamId"] = team_id + params = {"teamId": team_id} if team_id else None client.request("DELETE", f"/images/{image_name}/{image_tag}", params=params) console.print(f"[green]✓[/green] Deleted {image_name}:{image_tag}{context}") @@ -357,7 +358,7 @@ def delete_image( raise typer.Exit(1) except APIError as e: if "404" in str(e): - console.print(f"[red]Error: Image {image_reference} not found[/red]") + console.print(f"[red]Error: Image {original_reference} not found[/red]") elif "403" in str(e): console.print( "[red]Error: You don't have permission to delete this image. " From 4a137b1a23abb534e400d043d4e5d2da91141ea5 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 22 Jan 2026 18:04:40 -0800 Subject: [PATCH 6/6] better err handling --- packages/prime/src/prime_cli/commands/images.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index 1565ebc3..9d905283 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -326,8 +326,23 @@ def delete_image( namespace, rest = image_reference.split("/", 1) if namespace.startswith("team-"): # Extract team ID from the reference - team_id = namespace[5:] # Remove "team-" prefix + extracted_team_id = namespace[5:] # Remove "team-" prefix + if not extracted_team_id: + console.print( + "[red]Error: Invalid team image reference. " + "Expected format: team-{teamId}/imagename:tag[/red]" + ) + raise typer.Exit(1) + team_id = extracted_team_id image_reference = rest + else: + # Unrecognized namespace (not team-prefixed) + console.print( + f"[red]Error: Unrecognized image namespace '{namespace}'. " + "Use 'imagename:tag' for personal images or " + "'team-{{teamId}}/imagename:tag' for team images.[/red]" + ) + raise typer.Exit(1) # Validate image reference has a tag (after team-prefix parsing) if ":" not in image_reference: