From 3f687cb9c9ed095257637b0287cd84253049de3c Mon Sep 17 00:00:00 2001 From: Manveer Date: Mon, 26 Jan 2026 16:20:55 -0800 Subject: [PATCH 1/8] Check CI status before starting run --- packages/prime/src/prime_cli/api/rl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prime/src/prime_cli/api/rl.py b/packages/prime/src/prime_cli/api/rl.py index 77f19378..f3ba1b34 100644 --- a/packages/prime/src/prime_cli/api/rl.py +++ b/packages/prime/src/prime_cli/api/rl.py @@ -327,7 +327,7 @@ def get_environment_status(self, owner: str, name: str) -> Dict[str, Any]: """Get status for an environment including latest version and action info.""" try: response = self.client.get(f"/environmentshub/{owner}/{name}/status") - return response.get("data") or {} + return response.get("data", {}) except Exception as e: if hasattr(e, "response") and hasattr(e.response, "text"): raise APIError( From 2930d6aa886a360cd2b387097685cd915ba57810 Mon Sep 17 00:00:00 2001 From: Manveer Date: Mon, 26 Jan 2026 17:07:10 -0800 Subject: [PATCH 2/8] bugbot --- packages/prime/src/prime_cli/api/rl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prime/src/prime_cli/api/rl.py b/packages/prime/src/prime_cli/api/rl.py index f3ba1b34..77f19378 100644 --- a/packages/prime/src/prime_cli/api/rl.py +++ b/packages/prime/src/prime_cli/api/rl.py @@ -327,7 +327,7 @@ def get_environment_status(self, owner: str, name: str) -> Dict[str, Any]: """Get status for an environment including latest version and action info.""" try: response = self.client.get(f"/environmentshub/{owner}/{name}/status") - return response.get("data", {}) + return response.get("data") or {} except Exception as e: if hasattr(e, "response") and hasattr(e.response, "text"): raise APIError( From 858f679dd07871069a1d775ff7ebd3d7742e19c2 Mon Sep 17 00:00:00 2001 From: Manveer Date: Mon, 26 Jan 2026 14:19:05 -0800 Subject: [PATCH 3/8] Add ability to search envs + fetch CI status --- packages/prime/src/prime_cli/commands/env.py | 234 ++++++++++++++++++- 1 file changed, 224 insertions(+), 10 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/env.py b/packages/prime/src/prime_cli/commands/env.py index f1e3d623..b71d224d 100644 --- a/packages/prime/src/prime_cli/commands/env.py +++ b/packages/prime/src/prime_cli/commands/env.py @@ -169,6 +169,21 @@ def compute_content_hash(env_path: Path) -> str: return content_hasher.hexdigest() +def _format_ci_status(status: Optional[str]) -> Text: + """Format CI status with color coding.""" + if not status: + return Text("-", style="dim") + status_colors = { + "SUCCESS": "green", + "FAILED": "red", + "RUNNING": "yellow", + "PENDING": "yellow", + "CANCELLED": "dim", + } + color = status_colors.get(status.upper(), "white") + return Text(status, style=color) + + @app.command("list") def list_cmd( limit: int = typer.Option( @@ -180,10 +195,41 @@ def list_cmd( None, "--visibility", help="Filter by visibility (PUBLIC/PRIVATE)" ), output: str = typer.Option("table", "--output", help="Output format: table or json"), + search: Optional[str] = typer.Option( + None, "--search", "-s", help="Search by name or description" + ), + tag: Optional[List[str]] = typer.Option( + None, "--tag", "-t", help="Filter by tag (repeatable)" + ), + ci_status: Optional[str] = typer.Option( + None, "--ci-status", help="Filter by CI status (SUCCESS/FAILED/RUNNING/PENDING)" + ), + sort: str = typer.Option( + "created_at", "--sort", help="Sort by: name, created_at, updated_at" + ), + order: str = typer.Option("desc", "--order", help="Sort order: asc, desc"), + show_ci: bool = typer.Option(False, "--show-ci", help="Show CI status column"), ) -> None: - """List available verifiers environments""" + """List available verifiers environments + + Examples: + prime env list --search "test" + prime env list --ci-status SUCCESS --show-ci + prime env list --tag ml --tag nlp + prime env list --sort name --order asc + """ validate_output_format(output, console) + # Validate sort and order + if sort not in ("name", "created_at", "updated_at"): + console.print( + "[red]Error: --sort must be one of: name, created_at, updated_at[/red]" + ) + raise typer.Exit(1) + if order.lower() not in ("asc", "desc"): + console.print("[red]Error: --order must be one of: asc, desc[/red]") + raise typer.Exit(1) + try: client = APIClient(require_auth=False) @@ -191,11 +237,21 @@ def list_cmd( "include_teams": True, "limit": limit, "offset": offset, + "sort_by": sort, + "sort_order": order, } if owner: params["owner"] = owner if visibility: params["visibility"] = visibility + if search: + params["search"] = search + if tag: + params["tags"] = tag + if ci_status: + params["ci_status"] = ci_status + if show_ci or ci_status: + params["include_ci_status"] = True result = client.get("/environmentshub/", params=params) @@ -217,13 +273,17 @@ def list_cmd( for env in environments: owner_name = env["owner"]["name"] env_name = env["name"] - env_data.append( - { - "environment": f"{owner_name}/{env_name}", - "description": env.get("description", ""), - "visibility": env.get("visibility", ""), - } - ) + env_entry = { + "environment": f"{owner_name}/{env_name}", + "description": env.get("description", ""), + "visibility": env.get("visibility", ""), + } + if show_ci or ci_status: + env_entry["ci_status"] = env.get("latest_ci_status") + env_entry["version"] = env.get("latest_version") + if env.get("tags"): + env_entry["tags"] = env.get("tags") + env_data.append(env_entry) output_data = { "environments": env_data, @@ -238,14 +298,23 @@ def list_cmd( table.add_column("Environment", style="cyan") table.add_column("Description", style="green") table.add_column("Visibility", style="magenta") + if show_ci or ci_status: + table.add_column("Version", style="blue") + table.add_column("CI Status") for env in environments: owner_name = env["owner"]["name"] env_name = env["name"] env_id = f"{owner_name}/{env_name}" description = env.get("description", "") - visibility = env.get("visibility", "") - table.add_row(env_id, description, visibility) + vis = env.get("visibility", "") + + if show_ci or ci_status: + version = env.get("latest_version") or "-" + ci_text = _format_ci_status(env.get("latest_ci_status")) + table.add_row(env_id, description, vis, version, ci_text) + else: + table.add_row(env_id, description, vis) console.print(table) @@ -264,6 +333,151 @@ def list_cmd( raise typer.Exit(1) +def _format_time_ago(dt: Optional[datetime]) -> str: + """Format a datetime as a human-readable time ago string.""" + if not dt: + return "-" + now = datetime.utcnow() + if isinstance(dt, str): + # Parse ISO format string + dt = datetime.fromisoformat(dt.replace("Z", "+00:00").replace("+00:00", "")) + diff = now - dt + seconds = diff.total_seconds() + if seconds < 60: + return "just now" + minutes = seconds / 60 + if minutes < 60: + return f"{int(minutes)}m ago" + hours = minutes / 60 + if hours < 24: + return f"{int(hours)}h ago" + days = hours / 24 + if days < 30: + return f"{int(days)}d ago" + return dt.strftime("%Y-%m-%d") + + +def _format_duration(start: Optional[datetime], end: Optional[datetime]) -> str: + """Format duration between two datetimes.""" + if not start: + return "-" + if not end: + return "running..." + if isinstance(start, str): + start = datetime.fromisoformat(start.replace("Z", "+00:00").replace("+00:00", "")) + if isinstance(end, str): + end = datetime.fromisoformat(end.replace("Z", "+00:00").replace("+00:00", "")) + diff = end - start + seconds = diff.total_seconds() + if seconds < 60: + return f"{int(seconds)}s" + minutes = seconds / 60 + if minutes < 60: + return f"{int(minutes)}m {int(seconds % 60)}s" + hours = minutes / 60 + return f"{int(hours)}h {int(minutes % 60)}m" + + +@app.command("status") +def status_cmd( + env_id: str = typer.Argument(..., help="Environment ID (owner/name)"), + output: str = typer.Option("table", "--output", help="Output format: table or json"), + job_limit: int = typer.Option(5, "--jobs", "-j", help="Number of recent jobs to show"), +) -> None: + """Show CI/test status for an environment + + Examples: + prime env status owner/my-env + prime env status owner/my-env --jobs 10 + prime env status owner/my-env --output json + """ + validate_output_format(output, console) + + # Parse env_id + if "/" not in env_id: + console.print("[red]Error: Environment ID must be in format owner/name[/red]") + raise typer.Exit(1) + + parts = env_id.split("/", 1) + if len(parts) != 2: + console.print("[red]Error: Environment ID must be in format owner/name[/red]") + raise typer.Exit(1) + + owner_name, env_name = parts + + try: + client = APIClient(require_auth=False) + + result = client.get( + f"/environmentshub/{owner_name}/{env_name}/status", + params={"job_limit": job_limit}, + ) + + data = result.get("data", result) + + if output == "json": + output_data_as_json(data, console) + else: + # Header + console.print(f"\n[bold cyan]Environment:[/bold cyan] {owner_name}/{data.get('name', env_name)}") + if data.get("description"): + console.print(f"[dim]Description:[/dim] {data['description']}") + console.print(f"[dim]Visibility:[/dim] {data.get('visibility', 'UNKNOWN')}") + + # Latest Version section + console.print("\n[bold]Latest Version:[/bold]") + latest_version = data.get("latest_version") + if latest_version: + version_str = latest_version.get("semantic_version") or latest_version.get("content_hash", "")[:8] + console.print(f" Version: {version_str}") + console.print(f" Hash: {latest_version.get('content_hash', '-')[:12]}") + ci_status = latest_version.get("ci_status") + ci_text = _format_ci_status(ci_status) if ci_status else Text("-", style="dim") + created_at = latest_version.get("created_at") + time_ago = _format_time_ago(datetime.fromisoformat(created_at.replace("Z", "+00:00").replace("+00:00", "")) if created_at else None) + console.print(f" CI Status: ", end="") + console.print(ci_text, end="") + console.print(f" ({time_ago})") + else: + console.print(" [dim]No versions found[/dim]") + + # Recent Jobs section + console.print("\n[bold]Recent Jobs:[/bold]") + recent_jobs = data.get("recent_jobs", []) + if recent_jobs: + job_table = Table(show_header=True, header_style="bold") + job_table.add_column("ID", style="dim", max_width=10) + job_table.add_column("Type") + job_table.add_column("Status") + job_table.add_column("Started") + job_table.add_column("Duration") + + for job in recent_jobs: + job_id = job.get("id", "")[:10] + "..." + job_type = job.get("job_type", "-") + status_text = _format_ci_status(job.get("status")) + started_at = job.get("started_at") + started_str = _format_time_ago(datetime.fromisoformat(started_at.replace("Z", "+00:00").replace("+00:00", "")) if started_at else None) + duration = _format_duration( + datetime.fromisoformat(started_at.replace("Z", "+00:00").replace("+00:00", "")) if started_at else None, + datetime.fromisoformat(job.get("completed_at").replace("Z", "+00:00").replace("+00:00", "")) if job.get("completed_at") else None, + ) + job_table.add_row(job_id, job_type, status_text, started_str, duration) + + console.print(job_table) + else: + console.print(" [dim]No jobs found[/dim]") + + console.print() + + except APIError as e: + console.print(f"[red]Error: {e}[/red]") + raise typer.Exit(1) + except Exception as e: + console.print(f"[red]Unexpected error: {e}[/red]") + raise typer.Exit(1) + + @app.command() def push( path: str = typer.Option(".", "--path", "-p", help="Path to environment directory"), From e4df8f438a348698686d0dc3d3d38f779621f7c9 Mon Sep 17 00:00:00 2001 From: Manveer Date: Mon, 26 Jan 2026 18:03:49 -0800 Subject: [PATCH 4/8] Section eval subcommands --- packages/prime/src/prime_cli/commands/env.py | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/env.py b/packages/prime/src/prime_cli/commands/env.py index b71d224d..77ec8fa5 100644 --- a/packages/prime/src/prime_cli/commands/env.py +++ b/packages/prime/src/prime_cli/commands/env.py @@ -184,7 +184,7 @@ def _format_ci_status(status: Optional[str]) -> Text: return Text(status, style=color) -@app.command("list") +@app.command("list", rich_help_panel="Explore") def list_cmd( limit: int = typer.Option( DEFAULT_LIST_LIMIT, "--limit", "-l", help="Number of environments to show" @@ -378,13 +378,13 @@ def _format_duration(start: Optional[datetime], end: Optional[datetime]) -> str: return f"{int(hours)}h {int(minutes % 60)}m" -@app.command("status") +@app.command("status", rich_help_panel="Explore") def status_cmd( env_id: str = typer.Argument(..., help="Environment ID (owner/name)"), output: str = typer.Option("table", "--output", help="Output format: table or json"), job_limit: int = typer.Option(5, "--jobs", "-j", help="Number of recent jobs to show"), ) -> None: - """Show CI/test status for an environment + """Show CI/action status for an environment Examples: prime env status owner/my-env @@ -478,7 +478,7 @@ def status_cmd( raise typer.Exit(1) -@app.command() +@app.command(rich_help_panel="Manage") def push( path: str = typer.Option(".", "--path", "-p", help="Path to environment directory"), name: Optional[str] = typer.Option( @@ -1106,7 +1106,7 @@ def push( raise typer.Exit(1) -@app.command(no_args_is_help=True) +@app.command(no_args_is_help=True, rich_help_panel="Manage") def init( name: str = typer.Argument(..., help="Name of the new environment"), path: str = typer.Option( @@ -1141,7 +1141,7 @@ def init( raise typer.Exit(1) -@app.command(no_args_is_help=True) +@app.command(no_args_is_help=True, rich_help_panel="Manage") def pull( env_id: str = typer.Argument(..., help="Environment ID (owner/name or owner/name@version)"), target: Optional[str] = typer.Option(None, "--target", "-t", help="Target directory"), @@ -1496,7 +1496,7 @@ def get_install_command( raise ValueError(f"Unsupported package manager: {tool}. Use 'uv' or 'pip'.") -@app.command(no_args_is_help=True) +@app.command(no_args_is_help=True, rich_help_panel="Explore") def info( env_id: str = typer.Argument(..., help="Environment ID (owner/name)"), version: str = typer.Option("latest", "--version", "-v", help="Version to show"), @@ -1689,7 +1689,7 @@ def execute_install_command(cmd: List[str], env_id: str, version: str, tool: str console.print(f"\n[green]✓ Successfully installed {env_id}@{version}[/green]") -@app.command(no_args_is_help=True) +@app.command(no_args_is_help=True, rich_help_panel="Manage") def install( env_ids: List[str] = typer.Argument( ..., help="Environment ID(s) to install (owner/name or local name)" @@ -1959,7 +1959,7 @@ def execute_uninstall_command(cmd: List[str], env_name: str, tool: str) -> None: raise typer.Exit(1) -@app.command(no_args_is_help=True) +@app.command(no_args_is_help=True, rich_help_panel="Manage") def uninstall( env_name: str = typer.Argument(..., help="Environment name to uninstall"), with_tool: str = typer.Option( @@ -2020,7 +2020,7 @@ def uninstall( version_app = typer.Typer(help="Manage environment versions", no_args_is_help=True) -app.add_typer(version_app, name="version") +app.add_typer(version_app, name="version", rich_help_panel="Manage") @version_app.command("list", no_args_is_help=True) @@ -2176,7 +2176,7 @@ def delete_version( raise typer.Exit(1) -@app.command(no_args_is_help=True) +@app.command(no_args_is_help=True, rich_help_panel="Manage") def delete( env_id: str = typer.Argument(..., help="Environment ID to delete"), force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"), @@ -2823,6 +2823,7 @@ def run_eval( no_args_is_help=True, context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, deprecated=True, + rich_help_panel="Deprecated", ) def eval_env( ctx: typer.Context, From 629d6c8e8e2885ca876a1460ba890d4bb5deac22 Mon Sep 17 00:00:00 2001 From: Manveer Date: Mon, 26 Jan 2026 20:19:55 -0800 Subject: [PATCH 5/8] Add commands for actions --- packages/prime/src/prime_cli/commands/env.py | 403 +++++++++++++----- .../prime/src/prime_cli/utils/time_utils.py | 36 ++ 2 files changed, 342 insertions(+), 97 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/env.py b/packages/prime/src/prime_cli/commands/env.py index 77ec8fa5..4b680f35 100644 --- a/packages/prime/src/prime_cli/commands/env.py +++ b/packages/prime/src/prime_cli/commands/env.py @@ -7,6 +7,7 @@ import sys import tarfile import tempfile +import time import uuid import zipfile from datetime import datetime @@ -32,6 +33,7 @@ from ..utils.env_metadata import find_environment_metadata from ..utils.eval_push import push_eval_results_to_hub from ..utils.formatters import format_file_size +from ..utils.time_utils import format_time_ago app = typer.Typer(help="Manage verifiers environments", no_args_is_help=True) console = Console() @@ -42,6 +44,282 @@ DEFAULT_LIST_LIMIT = 20 MAX_TARBALL_SIZE_LIMIT = 250 * 1024 * 1024 # 250MB +# Actions subcommand app +actions_app = typer.Typer(help="Manage environment actions (CI jobs)", no_args_is_help=True) +app.add_typer(actions_app, name="actions") + +# Log cleaning pattern +ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + + +def _strip_ansi(text: str) -> str: + """Remove ANSI escape codes from text.""" + return ANSI_ESCAPE.sub("", text) + + +def _parse_environment_slug(environment: str) -> Tuple[str, str]: + """Parse owner/name from an environment slug. + + Args: + environment: Environment slug in format 'owner/name' + + Returns: + Tuple of (owner, name) + + Raises: + typer.Exit: If the slug is invalid + """ + if "/" not in environment: + console.print(f"[red]Invalid environment format: {environment}[/red]") + console.print("[dim]Use format: owner/environment-name[/dim]") + raise typer.Exit(1) + + parts = environment.split("/", 1) + if len(parts) != 2 or not parts[0] or not parts[1]: + console.print(f"[red]Invalid environment format: {environment}[/red]") + console.print("[dim]Use format: owner/environment-name[/dim]") + raise typer.Exit(1) + + return parts[0], parts[1] + + +@actions_app.command("list") +def actions_list( + environment: str = typer.Argument( + ..., + help="Environment slug (e.g., 'owner/environment-name')", + ), + version_id: Optional[str] = typer.Option( + None, + "--version-id", + "-v", + help="Filter by version ID", + ), + limit: int = typer.Option( + 20, + "--limit", + "-l", + help="Maximum number of actions to show", + ), + offset: int = typer.Option( + 0, + "--offset", + help="Offset for pagination", + ), + output: str = typer.Option( + "table", + "--output", + "-o", + help="Output format: table or json", + ), +) -> None: + """List actions (CI jobs) for an environment.""" + validate_output_format(output, console) + + owner, env_name = _parse_environment_slug(environment) + + try: + client = APIClient() + params = { + "limit": limit, + "offset": offset, + } + if version_id: + params["version_id"] = version_id + + response = client.get(f"/environmentshub/{owner}/{env_name}/actions", params=params) + data = response.get("data", {}) + + if output == "json": + output_data_as_json(data, console) + return + + actions = data.get("actions", []) + total = data.get("total", 0) + + if not actions: + console.print("[yellow]No actions found for this environment.[/yellow]") + return + + table = Table(title=f"Actions for {owner}/{env_name}") + table.add_column("ID", style="cyan", no_wrap=True) + table.add_column("Name", style="blue") + table.add_column("Status", style="yellow") + table.add_column("Version", style="dim") + table.add_column("Trigger", style="dim") + table.add_column("Created", style="dim") + + for action in actions: + action_id = action.get("id", "") + name = action.get("name") or action.get("job_type", "") + status = action.get("status", "") + + # Color the status + status_color = { + "SUCCESS": "[green]SUCCESS[/green]", + "FAILED": "[red]FAILED[/red]", + "RUNNING": "[yellow]RUNNING[/yellow]", + "PENDING": "[dim]PENDING[/dim]", + "CANCELLED": "[dim]CANCELLED[/dim]", + }.get(status, status) + + version = action.get("version", {}) + version_str = version.get("semantic_version") or version.get("content_hash", "")[:8] + trigger = action.get("trigger", "") + created = action.get("created_at", "") + if created: + created = format_time_ago(created) + + table.add_row(action_id, name, status_color, version_str, trigger, created) + + console.print(table) + console.print(f"[dim]Showing {len(actions)} of {total} actions[/dim]") + + except APIError as e: + console.print(f"[red]Error:[/red] {e}") + raise typer.Exit(1) + + +@actions_app.command("logs") +def actions_logs( + environment: str = typer.Argument( + ..., + help="Environment slug (e.g., 'owner/environment-name')", + ), + action_id: str = typer.Argument( + ..., + help="Action/job ID to get logs for", + ), + tail: int = typer.Option(1000, "--tail", "-n", help="Number of lines to show"), + follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"), +) -> None: + """Get logs for a specific action.""" + owner, env_name = _parse_environment_slug(environment) + + try: + client = APIClient() + + if follow: + console.print(f"[dim]Watching logs for action {action_id}... (Ctrl+C to stop)[/dim]\n") + last_logs = "" + consecutive_errors = 0 + + while True: + try: + response = client.get( + f"/environmentshub/{owner}/{env_name}/actions/{action_id}/logs", + params={"tail_lines": tail}, + ) + data = response.get("data", {}) + logs = _strip_ansi(data.get("logs", "")) + consecutive_errors = 0 + + if logs != last_logs: + old_lines = last_logs.splitlines() if last_logs else [] + new_lines = logs.splitlines() + + if not last_logs: + for line in new_lines: + console.print(line) + else: + overlap = 0 + max_overlap = min(len(old_lines), len(new_lines)) + for i in range(1, max_overlap + 1): + if old_lines[-i:] == new_lines[:i]: + overlap = i + for line in new_lines[overlap:]: + console.print(line) + + last_logs = logs + except APIError as e: + consecutive_errors += 1 + if "429" in str(e): + if consecutive_errors >= 3: + console.print("[yellow]Rate limited. Waiting 30s...[/yellow]") + time.sleep(30) + else: + time.sleep(10) + continue + raise + + time.sleep(5) + else: + response = client.get( + f"/environmentshub/{owner}/{env_name}/actions/{action_id}/logs", + params={"tail_lines": tail}, + ) + data = response.get("data", {}) + logs = _strip_ansi(data.get("logs", "")) + + if logs: + console.print(logs) + else: + console.print("[yellow]No logs available yet.[/yellow]") + + except KeyboardInterrupt: + console.print("\n[dim]Stopped watching logs.[/dim]") + except APIError as e: + console.print(f"[red]Error:[/red] {e}") + raise typer.Exit(1) + + +@actions_app.command("retry") +def actions_retry( + environment: str = typer.Argument( + ..., + help="Environment slug (e.g., 'owner/environment-name')", + ), + action_id: Optional[str] = typer.Argument( + None, + help="Action ID to retry (retries latest action if not provided)", + ), + output: str = typer.Option( + "table", + "--output", + "-o", + help="Output format: table or json", + ), +) -> None: + """Retry an action (integration test) for an environment. + + If no action ID is provided, retries the latest action. + """ + validate_output_format(output, console) + + owner, env_name = _parse_environment_slug(environment) + + try: + client = APIClient() + payload = {} + if action_id: + payload["action_id"] = action_id + + response = client.post( + f"/environmentshub/{owner}/{env_name}/actions/retry", + json=payload, + ) + data = response.get("data", {}) + + if output == "json": + output_data_as_json(data, console) + return + + if data.get("success"): + console.print("[green]Successfully triggered retry[/green]") + console.print(f"[dim]Job ID: {data.get('job_id')}[/dim]") + console.print(f"[dim]Version: {data.get('version_id')}[/dim]") + job_id = data.get('job_id') + console.print( + f"\n[dim]Use 'prime env actions logs {environment} {job_id}' to view logs[/dim]" + ) + else: + console.print(f"[red]Retry failed:[/red] {data.get('message', 'Unknown error')}") + raise typer.Exit(1) + + except APIError as e: + console.print(f"[red]Error:[/red] {e}") + raise typer.Exit(1) + def display_upstream_environment_info( env_path: Optional[Path] = None, environment_name: Optional[str] = None @@ -169,8 +447,8 @@ def compute_content_hash(env_path: Path) -> str: return content_hasher.hexdigest() -def _format_ci_status(status: Optional[str]) -> Text: - """Format CI status with color coding.""" +def _format_action_status(status: Optional[str]) -> Text: + """Format action status with color coding.""" if not status: return Text("-", style="dim") status_colors = { @@ -201,20 +479,20 @@ def list_cmd( tag: Optional[List[str]] = typer.Option( None, "--tag", "-t", help="Filter by tag (repeatable)" ), - ci_status: Optional[str] = typer.Option( - None, "--ci-status", help="Filter by CI status (SUCCESS/FAILED/RUNNING/PENDING)" + action_status: Optional[str] = typer.Option( + None, "--action-status", help="Filter by action status (SUCCESS/FAILED/RUNNING/PENDING)" ), sort: str = typer.Option( "created_at", "--sort", help="Sort by: name, created_at, updated_at" ), order: str = typer.Option("desc", "--order", help="Sort order: asc, desc"), - show_ci: bool = typer.Option(False, "--show-ci", help="Show CI status column"), + show_actions: bool = typer.Option(False, "--show-actions", help="Show action status column"), ) -> None: """List available verifiers environments Examples: prime env list --search "test" - prime env list --ci-status SUCCESS --show-ci + prime env list --action-status SUCCESS --show-actions prime env list --tag ml --tag nlp prime env list --sort name --order asc """ @@ -248,9 +526,9 @@ def list_cmd( params["search"] = search if tag: params["tags"] = tag - if ci_status: - params["ci_status"] = ci_status - if show_ci or ci_status: + if action_status: + params["ci_status"] = action_status + if show_actions or action_status: params["include_ci_status"] = True result = client.get("/environmentshub/", params=params) @@ -278,8 +556,8 @@ def list_cmd( "description": env.get("description", ""), "visibility": env.get("visibility", ""), } - if show_ci or ci_status: - env_entry["ci_status"] = env.get("latest_ci_status") + if show_actions or action_status: + env_entry["action_status"] = env.get("latest_ci_status") env_entry["version"] = env.get("latest_version") if env.get("tags"): env_entry["tags"] = env.get("tags") @@ -298,9 +576,9 @@ def list_cmd( table.add_column("Environment", style="cyan") table.add_column("Description", style="green") table.add_column("Visibility", style="magenta") - if show_ci or ci_status: + if show_actions or action_status: table.add_column("Version", style="blue") - table.add_column("CI Status") + table.add_column("Action Status") for env in environments: owner_name = env["owner"]["name"] @@ -309,10 +587,10 @@ def list_cmd( description = env.get("description", "") vis = env.get("visibility", "") - if show_ci or ci_status: + if show_actions or action_status: version = env.get("latest_version") or "-" - ci_text = _format_ci_status(env.get("latest_ci_status")) - table.add_row(env_id, description, vis, version, ci_text) + action_text = _format_action_status(env.get("latest_ci_status")) + table.add_row(env_id, description, vis, version, action_text) else: table.add_row(env_id, description, vis) @@ -333,62 +611,15 @@ def list_cmd( raise typer.Exit(1) -def _format_time_ago(dt: Optional[datetime]) -> str: - """Format a datetime as a human-readable time ago string.""" - if not dt: - return "-" - now = datetime.utcnow() - if isinstance(dt, str): - # Parse ISO format string - dt = datetime.fromisoformat(dt.replace("Z", "+00:00").replace("+00:00", "")) - diff = now - dt - seconds = diff.total_seconds() - if seconds < 60: - return "just now" - minutes = seconds / 60 - if minutes < 60: - return f"{int(minutes)}m ago" - hours = minutes / 60 - if hours < 24: - return f"{int(hours)}h ago" - days = hours / 24 - if days < 30: - return f"{int(days)}d ago" - return dt.strftime("%Y-%m-%d") - - -def _format_duration(start: Optional[datetime], end: Optional[datetime]) -> str: - """Format duration between two datetimes.""" - if not start: - return "-" - if not end: - return "running..." - if isinstance(start, str): - start = datetime.fromisoformat(start.replace("Z", "+00:00").replace("+00:00", "")) - if isinstance(end, str): - end = datetime.fromisoformat(end.replace("Z", "+00:00").replace("+00:00", "")) - diff = end - start - seconds = diff.total_seconds() - if seconds < 60: - return f"{int(seconds)}s" - minutes = seconds / 60 - if minutes < 60: - return f"{int(minutes)}m {int(seconds % 60)}s" - hours = minutes / 60 - return f"{int(hours)}h {int(minutes % 60)}m" - - @app.command("status", rich_help_panel="Explore") def status_cmd( env_id: str = typer.Argument(..., help="Environment ID (owner/name)"), output: str = typer.Option("table", "--output", help="Output format: table or json"), - job_limit: int = typer.Option(5, "--jobs", "-j", help="Number of recent jobs to show"), ) -> None: - """Show CI/action status for an environment + """Show action status for an environment Examples: prime env status owner/my-env - prime env status owner/my-env --jobs 10 prime env status owner/my-env --output json """ validate_output_format(output, console) @@ -410,7 +641,6 @@ def status_cmd( result = client.get( f"/environmentshub/{owner_name}/{env_name}/status", - params={"job_limit": job_limit}, ) data = result.get("data", result) @@ -431,42 +661,21 @@ def status_cmd( version_str = latest_version.get("semantic_version") or latest_version.get("content_hash", "")[:8] console.print(f" Version: {version_str}") console.print(f" Hash: {latest_version.get('content_hash', '-')[:12]}") - ci_status = latest_version.get("ci_status") - ci_text = _format_ci_status(ci_status) if ci_status else Text("-", style="dim") created_at = latest_version.get("created_at") - time_ago = _format_time_ago(datetime.fromisoformat(created_at.replace("Z", "+00:00").replace("+00:00", "")) if created_at else None) - console.print(f" CI Status: ", end="") - console.print(ci_text, end="") - console.print(f" ({time_ago})") + console.print(f" Created: {format_time_ago(created_at)}") else: console.print(" [dim]No versions found[/dim]") - # Recent Jobs section - console.print("\n[bold]Recent Jobs:[/bold]") - recent_jobs = data.get("recent_jobs", []) - if recent_jobs: - job_table = Table(show_header=True, header_style="bold") - job_table.add_column("ID", style="dim", max_width=10) - job_table.add_column("Type") - job_table.add_column("Status") - job_table.add_column("Started") - job_table.add_column("Duration") - - for job in recent_jobs: - job_id = job.get("id", "")[:10] + "..." - job_type = job.get("job_type", "-") - status_text = _format_ci_status(job.get("status")) - started_at = job.get("started_at") - started_str = _format_time_ago(datetime.fromisoformat(started_at.replace("Z", "+00:00").replace("+00:00", "")) if started_at else None) - duration = _format_duration( - datetime.fromisoformat(started_at.replace("Z", "+00:00").replace("+00:00", "")) if started_at else None, - datetime.fromisoformat(job.get("completed_at").replace("Z", "+00:00").replace("+00:00", "")) if job.get("completed_at") else None, - ) - job_table.add_row(job_id, job_type, status_text, started_str, duration) - - console.print(job_table) - else: - console.print(" [dim]No jobs found[/dim]") + # Action status section + action_data = data.get("action") + if action_data: + console.print("\n[bold]Action Status:[/bold]") + action_status_value = action_data.get("status") + action_text = _format_action_status(action_status_value) + console.print(f" Status: ", end="") + console.print(action_text) + if action_data.get("job_id"): + console.print(f" Job ID: [dim]{action_data.get('job_id')}[/dim]") console.print() diff --git a/packages/prime/src/prime_cli/utils/time_utils.py b/packages/prime/src/prime_cli/utils/time_utils.py index 58019ef9..dc6b56d5 100644 --- a/packages/prime/src/prime_cli/utils/time_utils.py +++ b/packages/prime/src/prime_cli/utils/time_utils.py @@ -34,6 +34,42 @@ def human_age(created: datetime) -> str: return f"{days}d" +def format_time_ago(dt: Union[datetime, str, None]) -> str: + """Format a datetime as a human-readable 'time ago' string. + + Args: + dt: A datetime object, ISO format string, or None + + Returns: + A human-readable string like "just now", "5m ago", "2h ago", "3d ago", + or the date in YYYY-MM-DD format for dates older than 30 days. + Returns "-" if dt is None. + """ + if not dt: + return "-" + + if isinstance(dt, str): + # Parse ISO format string, handling various formats + dt = datetime.fromisoformat(dt.replace("Z", "+00:00")) + + diff = now_utc() - to_utc(dt) + total_seconds = int(diff.total_seconds()) + + if total_seconds < 60: + return "just now" + elif total_seconds < 3600: + minutes = total_seconds // 60 + return f"{minutes}m ago" + elif total_seconds < 86400: + hours = total_seconds // 3600 + return f"{hours}h ago" + elif total_seconds < 2592000: # 30 days + days = total_seconds // 86400 + return f"{days}d ago" + else: + return to_utc(dt).strftime("%Y-%m-%d") + + def iso_timestamp(dt: Union[datetime, str]) -> str: """Convert datetime or ISO string to standardized timestamp format.""" if isinstance(dt, str): From f4c0c019e3dd805a9763384d28b2199a34f59bd8 Mon Sep 17 00:00:00 2001 From: Manveer Date: Mon, 26 Jan 2026 20:41:33 -0800 Subject: [PATCH 6/8] Improve suggested actions when prime rl fails cus of actions --- packages/prime/src/prime_cli/commands/env.py | 14 +++++++------- packages/prime/src/prime_cli/commands/rl.py | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/env.py b/packages/prime/src/prime_cli/commands/env.py index 4b680f35..57a90b05 100644 --- a/packages/prime/src/prime_cli/commands/env.py +++ b/packages/prime/src/prime_cli/commands/env.py @@ -44,9 +44,9 @@ DEFAULT_LIST_LIMIT = 20 MAX_TARBALL_SIZE_LIMIT = 250 * 1024 * 1024 # 250MB -# Actions subcommand app -actions_app = typer.Typer(help="Manage environment actions (CI jobs)", no_args_is_help=True) -app.add_typer(actions_app, name="actions") +# Action subcommand app +action_app = typer.Typer(help="Manage environment actions (CI jobs)", no_args_is_help=True) +app.add_typer(action_app, name="action", rich_help_panel="Manage") # Log cleaning pattern ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") @@ -83,7 +83,7 @@ def _parse_environment_slug(environment: str) -> Tuple[str, str]: return parts[0], parts[1] -@actions_app.command("list") +@action_app.command("list") def actions_list( environment: str = typer.Argument( ..., @@ -180,7 +180,7 @@ def actions_list( raise typer.Exit(1) -@actions_app.command("logs") +@action_app.command("logs") def actions_logs( environment: str = typer.Argument( ..., @@ -263,7 +263,7 @@ def actions_logs( raise typer.Exit(1) -@actions_app.command("retry") +@action_app.command("retry") def actions_retry( environment: str = typer.Argument( ..., @@ -310,7 +310,7 @@ def actions_retry( console.print(f"[dim]Version: {data.get('version_id')}[/dim]") job_id = data.get('job_id') console.print( - f"\n[dim]Use 'prime env actions logs {environment} {job_id}' to view logs[/dim]" + f"\n[dim]Use 'prime env action logs {environment} {job_id}' to view logs[/dim]" ) else: console.print(f"[red]Retry failed:[/red] {data.get('message', 'Unknown error')}") diff --git a/packages/prime/src/prime_cli/commands/rl.py b/packages/prime/src/prime_cli/commands/rl.py index 0eb4ad3c..8baf02d3 100644 --- a/packages/prime/src/prime_cli/commands/rl.py +++ b/packages/prime/src/prime_cli/commands/rl.py @@ -557,7 +557,8 @@ def warn(msg: str) -> None: owner, name = env_id_base.split("/", 1) url = f"{app_config.frontend_url}/dashboard/environments/{owner}/{name}/actions" console.print(f" [red]✗[/red] {env_id}") - console.print(f" [link={url}]{url}[/link]\n") + console.print(f" [dim]Details: prime env action logs {env_id_base}[/dim]") + console.print(f" [dim]View at: [link={url}]{url}[/link][/dim]\n") console.print( "[yellow]This usually means the environment doesn't compile or run, " From 91abff65f7ed38148af3f34181d055cd8f7a33cf Mon Sep 17 00:00:00 2001 From: Manveer Date: Tue, 27 Jan 2026 12:15:31 -0800 Subject: [PATCH 7/8] bugbot --- packages/prime/src/prime_cli/commands/env.py | 34 ++++++++------------ packages/prime/src/prime_cli/commands/rl.py | 2 +- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/env.py b/packages/prime/src/prime_cli/commands/env.py index 57a90b05..ce22ad42 100644 --- a/packages/prime/src/prime_cli/commands/env.py +++ b/packages/prime/src/prime_cli/commands/env.py @@ -33,7 +33,7 @@ from ..utils.env_metadata import find_environment_metadata from ..utils.eval_push import push_eval_results_to_hub from ..utils.formatters import format_file_size -from ..utils.time_utils import format_time_ago +from ..utils.time_utils import format_time_ago, iso_timestamp app = typer.Typer(help="Manage verifiers environments", no_args_is_help=True) console = Console() @@ -163,8 +163,8 @@ def actions_list( "CANCELLED": "[dim]CANCELLED[/dim]", }.get(status, status) - version = action.get("version", {}) - version_str = version.get("semantic_version") or version.get("content_hash", "")[:8] + version = action.get("version") or {} + version_str = version.get("semantic_version") or (version.get("content_hash") or "")[:8] trigger = action.get("trigger", "") created = action.get("created_at", "") if created: @@ -211,7 +211,7 @@ def actions_logs( params={"tail_lines": tail}, ) data = response.get("data", {}) - logs = _strip_ansi(data.get("logs", "")) + logs = _strip_ansi(data.get("logs") or "") consecutive_errors = 0 if logs != last_logs: @@ -249,7 +249,7 @@ def actions_logs( params={"tail_lines": tail}, ) data = response.get("data", {}) - logs = _strip_ansi(data.get("logs", "")) + logs = _strip_ansi(data.get("logs") or "") if logs: console.print(logs) @@ -625,16 +625,7 @@ def status_cmd( validate_output_format(output, console) # Parse env_id - if "/" not in env_id: - console.print("[red]Error: Environment ID must be in format owner/name[/red]") - raise typer.Exit(1) - - parts = env_id.split("/", 1) - if len(parts) != 2: - console.print("[red]Error: Environment ID must be in format owner/name[/red]") - raise typer.Exit(1) - - owner_name, env_name = parts + owner_name, env_name = _parse_environment_slug(env_id) try: client = APIClient(require_auth=False) @@ -649,7 +640,8 @@ def status_cmd( output_data_as_json(data, console) else: # Header - console.print(f"\n[bold cyan]Environment:[/bold cyan] {owner_name}/{data.get('name', env_name)}") + env_display_name = data.get("name", env_name) + console.print(f"\n[bold cyan]Environment:[/bold cyan] {owner_name}/{env_display_name}") if data.get("description"): console.print(f"[dim]Description:[/dim] {data['description']}") console.print(f"[dim]Visibility:[/dim] {data.get('visibility', 'UNKNOWN')}") @@ -658,9 +650,10 @@ def status_cmd( console.print("\n[bold]Latest Version:[/bold]") latest_version = data.get("latest_version") if latest_version: - version_str = latest_version.get("semantic_version") or latest_version.get("content_hash", "")[:8] + content_hash = latest_version.get("content_hash") or "" + version_str = latest_version.get("semantic_version") or content_hash[:8] console.print(f" Version: {version_str}") - console.print(f" Hash: {latest_version.get('content_hash', '-')[:12]}") + console.print(f" Hash: {(latest_version.get('content_hash') or '-')[:12]}") created_at = latest_version.get("created_at") console.print(f" Created: {format_time_ago(created_at)}") else: @@ -672,7 +665,7 @@ def status_cmd( console.print("\n[bold]Action Status:[/bold]") action_status_value = action_data.get("status") action_text = _format_action_status(action_status_value) - console.print(f" Status: ", end="") + console.print(" Status: ", end="") console.print(action_text) if action_data.get("job_id"): console.print(f" Job ID: [dim]{action_data.get('job_id')}[/dim]") @@ -2287,8 +2280,7 @@ def list_versions( # Format date nicely if it's a full timestamp try: if "T" in created_date: - dt = datetime.fromisoformat(created_date.replace("Z", "+00:00")) - created_date = dt.strftime("%Y-%m-%d %H:%M") + created_date = iso_timestamp(created_date) except Exception: pass diff --git a/packages/prime/src/prime_cli/commands/rl.py b/packages/prime/src/prime_cli/commands/rl.py index 8baf02d3..69fcd02c 100644 --- a/packages/prime/src/prime_cli/commands/rl.py +++ b/packages/prime/src/prime_cli/commands/rl.py @@ -557,7 +557,7 @@ def warn(msg: str) -> None: owner, name = env_id_base.split("/", 1) url = f"{app_config.frontend_url}/dashboard/environments/{owner}/{name}/actions" console.print(f" [red]✗[/red] {env_id}") - console.print(f" [dim]Details: prime env action logs {env_id_base}[/dim]") + console.print(f" [dim]Details: prime env action list {env_id_base}[/dim]") console.print(f" [dim]View at: [link={url}]{url}[/link][/dim]\n") console.print( From c32730c2b3c3fef370a1dba0b98d7e75f856a2f4 Mon Sep 17 00:00:00 2001 From: Manveer Date: Wed, 28 Jan 2026 16:35:24 -0800 Subject: [PATCH 8/8] PR comments --- packages/prime/src/prime_cli/commands/env.py | 80 +++++++++++++------ .../prime/src/prime_cli/commands/evals.py | 6 +- .../prime/src/prime_cli/commands/images.py | 3 + .../prime/src/prime_cli/commands/sandbox.py | 7 +- 4 files changed, 66 insertions(+), 30 deletions(-) diff --git a/packages/prime/src/prime_cli/commands/env.py b/packages/prime/src/prime_cli/commands/env.py index ce22ad42..a9022fff 100644 --- a/packages/prime/src/prime_cli/commands/env.py +++ b/packages/prime/src/prime_cli/commands/env.py @@ -483,25 +483,36 @@ def list_cmd( None, "--action-status", help="Filter by action status (SUCCESS/FAILED/RUNNING/PENDING)" ), sort: str = typer.Option( - "created_at", "--sort", help="Sort by: name, created_at, updated_at" + "created_at", "--sort", help="Sort by: name, created_at, updated_at, stars" ), order: str = typer.Option("desc", "--order", help="Sort order: asc, desc"), show_actions: bool = typer.Option(False, "--show-actions", help="Show action status column"), + starred: bool = typer.Option( + False, "--starred", help="Filter to only environments you have starred" + ), + mine: bool = typer.Option( + False, "--mine", help="Filter to only your own environments (personal + team)" + ), ) -> None: - """List available verifiers environments + """List environments from the hub. + + By default, shows all public environments. If authenticated, also includes + private environments you have access to. Use --starred or --mine to filter. + \b Examples: - prime env list --search "test" - prime env list --action-status SUCCESS --show-actions - prime env list --tag ml --tag nlp - prime env list --sort name --order asc + prime env list # All public environments + prime env list --starred # Your starred environments + prime env list --mine # Your own environments + prime env list --search "math" # Search by name/description + prime env list --sort stars # Sort by most starred """ validate_output_format(output, console) # Validate sort and order - if sort not in ("name", "created_at", "updated_at"): + if sort not in ("name", "created_at", "updated_at", "stars"): console.print( - "[red]Error: --sort must be one of: name, created_at, updated_at[/red]" + "[red]Error: --sort must be one of: name, created_at, updated_at, stars[/red]" ) raise typer.Exit(1) if order.lower() not in ("asc", "desc"): @@ -509,7 +520,9 @@ def list_cmd( raise typer.Exit(1) try: - client = APIClient(require_auth=False) + # Require auth if filtering by starred or mine + require_auth = starred or mine + client = APIClient(require_auth=require_auth) params: Dict[str, Any] = { "include_teams": True, @@ -530,6 +543,10 @@ def list_cmd( params["ci_status"] = action_status if show_actions or action_status: params["include_ci_status"] = True + if starred: + params["starred_only"] = True + if mine: + params["mine_only"] = True result = client.get("/environmentshub/", params=params) @@ -555,10 +572,12 @@ def list_cmd( "environment": f"{owner_name}/{env_name}", "description": env.get("description", ""), "visibility": env.get("visibility", ""), + "version": env.get("latest_version"), + "stars": env.get("stars", 0), + "updated_at": env.get("updated_at"), } if show_actions or action_status: env_entry["action_status"] = env.get("latest_ci_status") - env_entry["version"] = env.get("latest_version") if env.get("tags"): env_entry["tags"] = env.get("tags") env_data.append(env_entry) @@ -575,9 +594,10 @@ def list_cmd( table = Table(title=f"Environments (Total: {total})") table.add_column("Environment", style="cyan") table.add_column("Description", style="green") - table.add_column("Visibility", style="magenta") + table.add_column("Version", style="blue") + table.add_column("Stars", style="yellow", justify="right") + table.add_column("Updated", style="dim") if show_actions or action_status: - table.add_column("Version", style="blue") table.add_column("Action Status") for env in environments: @@ -585,14 +605,22 @@ def list_cmd( env_name = env["name"] env_id = f"{owner_name}/{env_name}" description = env.get("description", "") - vis = env.get("visibility", "") + version = env.get("latest_version") or "-" + stars = str(env.get("stars", 0)) + updated_at = env.get("updated_at", "") + if updated_at: + # Format as short date + try: + dt = datetime.fromisoformat(updated_at.replace("Z", "+00:00")) + updated_at = dt.strftime("%Y-%m-%d") + except (ValueError, AttributeError): + pass if show_actions or action_status: - version = env.get("latest_version") or "-" action_text = _format_action_status(env.get("latest_ci_status")) - table.add_row(env_id, description, vis, version, action_text) + table.add_row(env_id, description, version, stars, updated_at, action_text) else: - table.add_row(env_id, description, vis) + table.add_row(env_id, description, version, stars, updated_at) console.print(table) @@ -616,8 +644,9 @@ def status_cmd( env_id: str = typer.Argument(..., help="Environment ID (owner/name)"), output: str = typer.Option("table", "--output", help="Output format: table or json"), ) -> None: - """Show action status for an environment + """Show action status for an environment. + \b Examples: prime env status owner/my-env prime env status owner/my-env --output json @@ -1913,16 +1942,16 @@ def install( help="Don't upgrade existing packages. Useful with locked dependencies (uv.lock).", ), ) -> None: - """Install a verifiers environment + """Install a verifiers environment. + \b Examples: - prime env install gsm8k # local editable install from ./environments - prime env install gsm8k -p /path/to/envs # local install from custom path - prime env install owner/environment # install from Prime Hub - prime env install owner/environment@0.2.3 + prime env install gsm8k # local install from ./environments + prime env install gsm8k -p /path/to/envs # local install from custom path + prime env install owner/environment # install from Prime Hub + prime env install owner/environment@0.2.3 # specific version prime env install owner/environment --with pip - prime env install owner/environment --no-upgrade - prime env install owner/environment1 owner/environment2 owner/environment3 + prime env install env1 env2 env3 # install multiple """ try: client = APIClient(require_auth=False) @@ -2170,8 +2199,9 @@ def uninstall( help="Package manager to use (uv or pip)", ), ) -> None: - """Uninstall a verifiers environment + """Uninstall a verifiers environment. + \b Examples: prime env uninstall environment prime env uninstall environment --with pip diff --git a/packages/prime/src/prime_cli/commands/evals.py b/packages/prime/src/prime_cli/commands/evals.py index 030b8ddd..eec51e7f 100644 --- a/packages/prime/src/prime_cli/commands/evals.py +++ b/packages/prime/src/prime_cli/commands/evals.py @@ -429,6 +429,7 @@ def push_eval( The directory must contain metadata.json and results.jsonl files. + \b Examples: prime eval push # Push current dir or auto-discover prime eval push outputs/evals/gsm8k--gpt-4/abc123 # Push specific directory @@ -660,9 +661,10 @@ def run_eval_cmd( """ Run verifiers' vf-eval with Prime Inference. + \b Examples: - prime eval run primeintellect/wordle -m openai/gpt-4.1-mini -n 5 - prime eval run wordle -m openai/gpt-4.1-mini -n 2 -r 3 -t 1024 -T 0.7 + prime eval run primeintellect/wordle -m openai/gpt-4.1-mini -n 5 + prime eval run wordle -m openai/gpt-4.1-mini -n 2 -r 3 -t 1024 -T 0.7 """ run_eval( environment=environment, diff --git a/packages/prime/src/prime_cli/commands/images.py b/packages/prime/src/prime_cli/commands/images.py index 4916bd62..5b8e25e0 100644 --- a/packages/prime/src/prime_cli/commands/images.py +++ b/packages/prime/src/prime_cli/commands/images.py @@ -40,6 +40,7 @@ def push_image( """ Build and push a Docker image to Prime Intellect registry. + \b Examples: prime images push myapp:v1.0.0 prime images push myapp:latest --dockerfile custom.Dockerfile @@ -186,6 +187,7 @@ def list_images( """ List all images you've pushed to Prime Intellect registry. + \b Examples: prime images list prime images list --output json @@ -279,6 +281,7 @@ def delete_image( Note: This removes the database record but does not delete the actual image from Google Artifact Registry. + \b Examples: prime images delete myapp:v1.0.0 prime images delete myapp:latest --yes diff --git a/packages/prime/src/prime_cli/commands/sandbox.py b/packages/prime/src/prime_cli/commands/sandbox.py index ae030006..a1a36825 100644 --- a/packages/prime/src/prime_cli/commands/sandbox.py +++ b/packages/prime/src/prime_cli/commands/sandbox.py @@ -1160,9 +1160,10 @@ def ssh_connect( This command creates a SSH session with an ephemeral key and cleans up on disconnect. - Examples:\n - prime sandbox ssh sb_abc123\n - prime sandbox ssh sb_abc123 -- -L 3000:localhost:3000\n + \b + Examples: + prime sandbox ssh sb_abc123 + prime sandbox ssh sb_abc123 -- -L 3000:localhost:3000 """ session_id: Optional[str] = None sandbox_client: Optional[SandboxClient] = None